@sanity/cli 8.0.2 → 8.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SanityHelp.js.map +1 -1
- package/dist/actions/backup/assertDatasetExist.js +2 -1
- package/dist/actions/backup/assertDatasetExist.js.map +1 -1
- package/dist/actions/build/buildStudio.js +3 -2
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/build/eventListenerFactory.js +7 -6
- package/dist/actions/build/eventListenerFactory.js.map +1 -1
- package/dist/actions/build/shouldAutoUpdate.js +2 -1
- package/dist/actions/build/shouldAutoUpdate.js.map +1 -1
- package/dist/actions/cors/filterAndValidateOrigin.js +3 -2
- package/dist/actions/cors/filterAndValidateOrigin.js.map +1 -1
- package/dist/actions/deploy/createUserApplication.js +2 -1
- package/dist/actions/deploy/createUserApplication.js.map +1 -1
- package/dist/actions/deploy/deployRunner.js +3 -2
- package/dist/actions/deploy/deployRunner.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +3 -3
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/findUserApplication.js +8 -7
- package/dist/actions/deploy/findUserApplication.js.map +1 -1
- package/dist/actions/dev/servers/startAppDevServer.js +2 -1
- package/dist/actions/dev/servers/startAppDevServer.js.map +1 -1
- package/dist/actions/dev/servers/startStudioDevServer.js +3 -3
- package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
- package/dist/actions/documents/validate.js +5 -2
- package/dist/actions/documents/validate.js.map +1 -1
- package/dist/actions/init/env/createOrAppendEnvVars.js +2 -1
- package/dist/actions/init/env/createOrAppendEnvVars.js.map +1 -1
- package/dist/actions/init/env/parseAndUpdateEnvVars.js +2 -2
- package/dist/actions/init/env/parseAndUpdateEnvVars.js.map +1 -1
- package/dist/actions/schema/extractSchema.js +2 -2
- package/dist/actions/schema/extractSchema.js.map +1 -1
- package/dist/actions/schema/extractSchemaWatcher.js +2 -1
- package/dist/actions/schema/extractSchemaWatcher.js.map +1 -1
- package/dist/actions/undeploy/runUndeploy.js +2 -2
- package/dist/actions/undeploy/runUndeploy.js.map +1 -1
- package/dist/commands/backups/list.js +5 -8
- package/dist/commands/backups/list.js.map +1 -1
- package/dist/commands/datasets/copy.js +5 -7
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/docs/search.js +2 -2
- package/dist/commands/docs/search.js.map +1 -1
- package/dist/commands/mcp/configure.js +2 -2
- package/dist/commands/mcp/configure.js.map +1 -1
- package/dist/commands/typegen/generate.js +6 -6
- package/dist/commands/typegen/generate.js.map +1 -1
- package/dist/services/mintProject.js +27 -3
- package/dist/services/mintProject.js.map +1 -1
- package/dist/util/appId.js +2 -1
- package/dist/util/appId.js.map +1 -1
- package/dist/util/dates.js +125 -0
- package/dist/util/dates.js.map +1 -0
- package/dist/util/getCliVersion.js +1 -11
- package/dist/util/getCliVersion.js.map +1 -1
- package/oclif.manifest.json +707 -707
- package/package.json +4 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/typegen/generate.ts"],"sourcesContent":["import {stat} from 'node:fs/promises'\nimport {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {CLIError} from '@oclif/core/errors'\nimport {SanityCommand} from '@sanity/cli-core'\nimport {spinner, type SpinnerInstance} from '@sanity/cli-core/ux'\nimport {\n configDefinition,\n readConfig,\n runTypegenGenerate,\n runTypegenWatcher,\n type TypeGenConfig,\n TypegenWatchModeTrace,\n TypesGeneratedTrace,\n} from '@sanity/codegen'\nimport omit from 'lodash-es/omit.js'\nimport once from 'lodash-es/once.js'\n\nimport {createTypegenProgressRenderer} from '../../actions/typegen/renderTypegenProgress.js'\n\nconst description = `Sanity TypeGen\n\n${styleText('bold', 'Configuration:')}\nThis command can utilize configuration settings defined in a \\`sanity-typegen.json\\` file. These settings include:\n\n- \"path\": Specifies a glob pattern to locate your TypeScript or JavaScript files.\n Default: \"./src/**/*.{ts,tsx,js,jsx}\"\n\n- \"schema\": Defines the path to your Sanity schema file. This file should be generated using the \\`sanity schema extract\\` command.\n Default: \"schema.json\"\n\n- \"generates\": Indicates the path where the generated TypeScript type definitions will be saved.\n Default: \"./sanity.types.ts\"\n\nThe default configuration values listed above are used if not overridden in your \\`sanity-typegen.json\\` configuration file. To customize the behavior of the type generation, adjust these properties in the configuration file according to your project's needs.\n\n${styleText('bold', 'Note:')}\n- The \\`sanity schema extract\\` command is a prerequisite for extracting your Sanity Studio schema into a \\`schema.json\\` file, which is then used by the \\`sanity typegen generate\\` command to generate type definitions.`.trim()\n\nexport class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerateCommand> {\n static override description = description\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: `Generate TypeScript type definitions from a Sanity Studio schema extracted using the \\`sanity schema extract\\` command.`,\n },\n ]\n\n static override flags = {\n 'config-path': Flags.string({\n description:\n '[Default: sanity-typegen.json] Specifies the path to the typegen configuration file. This file should be a JSON file that contains settings for the type generation process.',\n }),\n watch: Flags.boolean({\n default: false,\n description: '[Default: false] Run the typegen in watch mode',\n }),\n }\n\n static override hiddenAliases: string[] = ['typegen:generate']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(TypegenGenerateCommand)\n\n if (flags.watch) {\n await this.runWatcher()\n return\n }\n\n await this.runSingle()\n }\n\n private async getConfig(): Promise<{\n config: TypeGenConfig\n path?: string\n type: 'cli' | 'legacy'\n workDir: string\n }> {\n const spin = spinner({}).start('Loading config…')\n\n try {\n const {flags} = await this.parse(TypegenGenerateCommand)\n const rootDir = await this.getProjectRoot()\n const config = await this.getCliConfig()\n\n const configPath = flags['config-path']\n const workDir = rootDir.directory\n\n const legacyConfigPath = configPath || 'sanity-typegen.json'\n let hasLegacyConfig = false\n\n try {\n const file = await stat(legacyConfigPath)\n hasLegacyConfig = file.isFile()\n } catch (err) {\n if (err instanceof Error && 'code' in err && err.code === 'ENOENT' && configPath) {\n spin.fail()\n this.error(`Typegen config file not found: ${configPath}`, {exit: 1})\n }\n\n if (err instanceof Error && 'code' in err && err.code !== 'ENOENT') {\n spin.fail()\n this.error(`Error when checking if typegen config file exists: ${legacyConfigPath}`, {\n exit: 1,\n })\n }\n }\n\n if (config?.typegen && hasLegacyConfig) {\n spin.warn(\n styleText(\n 'yellow',\n `You've specified typegen in your Sanity CLI config, but also have a typegen config.\n\n The config from the Sanity CLI config is used.\n `,\n ),\n )\n\n return {\n config: configDefinition.parse(config.typegen || {}),\n path: rootDir.path,\n type: 'cli',\n workDir,\n }\n }\n\n if (hasLegacyConfig) {\n spin.warn(\n styleText(\n 'yellow',\n `The separate typegen config has been deprecated. Use \\`typegen\\` in the sanity CLI config instead.\n\n See: https://www.sanity.io/docs/help/configuring-typegen-in-sanity-cli-config`,\n ),\n )\n return {\n config: await readConfig(legacyConfigPath),\n path: legacyConfigPath,\n type: 'legacy',\n workDir,\n }\n }\n\n spin.succeed(`Config loaded from sanity.cli.ts`)\n\n return {\n config: configDefinition.parse(config.typegen || {}),\n path: rootDir.path,\n type: 'cli',\n workDir,\n }\n } catch (err) {\n // The config-file checks above already reported a user-facing error via\n // `this.error()` (an oclif CLIError). Re-throw those as-is instead of\n // wrapping them a second time and failing the spinner twice.\n if (err instanceof CLIError) throw err\n spin.fail()\n this.error(`An error occurred during config loading: ${err}`, {exit: 1})\n }\n }\n\n private async runSingle(): Promise<void> {\n const trace = this.telemetry.trace(TypesGeneratedTrace)\n let spin: SpinnerInstance | undefined\n\n try {\n const {config: typegenConfig, type: typegenConfigMethod, workDir} = await this.getConfig()\n trace.start()\n\n spin = spinner({}).start('Loading schema…')\n const result = await runTypegenGenerate({\n config: typegenConfig,\n onProgress: createTypegenProgressRenderer(spin, {\n formatGeneratedCode: typegenConfig.formatGeneratedCode,\n generates: typegenConfig.generates,\n schema: typegenConfig.schema,\n }),\n workDir,\n })\n\n const traceStats = omit(result, 'code', 'duration')\n\n trace.log({\n configMethod: typegenConfigMethod,\n configOverloadClientMethods: typegenConfig.overloadClientMethods,\n ...traceStats,\n })\n trace.complete()\n } catch (error) {\n if (spin?.isSpinning) {\n spin.fail()\n }\n trace.error(error instanceof Error ? error : new Error(String(error)))\n this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {exit: 1})\n }\n }\n\n private async runWatcher(): Promise<void> {\n const trace = this.telemetry.trace(TypegenWatchModeTrace)\n let spin: SpinnerInstance | undefined\n\n try {\n const {config: typegenConfig, workDir} = await this.getConfig()\n trace.start()\n\n let resolve: () => void = () => {}\n const promise = new Promise<void>((res) => {\n resolve = res\n })\n\n const watchSpin = spinner({}).start('Loading schema…')\n spin = watchSpin\n const rendererContext = {\n formatGeneratedCode: typegenConfig.formatGeneratedCode,\n generates: typegenConfig.generates,\n schema: typegenConfig.schema,\n }\n let renderProgress = createTypegenProgressRenderer(watchSpin, rendererContext)\n\n const typegenWatcher = runTypegenWatcher({\n config: typegenConfig,\n onProgress: (event) => {\n // 'schemaLoaded' is the first event of every generation cycle; the\n // renderer accumulates per-run state, so start each cycle fresh.\n if (event.type === 'schemaLoaded') {\n renderProgress = createTypegenProgressRenderer(watchSpin, rendererContext)\n }\n renderProgress(event)\n },\n workDir,\n })\n\n const stop = once(async () => {\n process.off('SIGINT', stop)\n process.off('SIGTERM', stop)\n\n if (watchSpin.isSpinning) {\n watchSpin.stop()\n }\n\n trace.log({\n step: 'stopped',\n ...typegenWatcher.getStats(),\n })\n trace.complete()\n\n try {\n await typegenWatcher.stop()\n } catch {\n // We're shutting down; a failure to close the watcher shouldn't hang\n // the process or surface as an unhandled rejection.\n }\n resolve()\n })\n\n process.on('SIGINT', stop)\n process.on('SIGTERM', stop)\n\n await promise\n } catch (error) {\n if (spin?.isSpinning) {\n spin.fail()\n }\n trace.error(error instanceof Error ? error : new Error(String(error)))\n this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {exit: 1})\n }\n }\n}\n"],"names":["stat","styleText","Flags","CLIError","SanityCommand","spinner","configDefinition","readConfig","runTypegenGenerate","runTypegenWatcher","TypegenWatchModeTrace","TypesGeneratedTrace","omit","once","createTypegenProgressRenderer","description","trim","TypegenGenerateCommand","examples","command","flags","string","watch","boolean","default","hiddenAliases","run","parse","runWatcher","runSingle","getConfig","spin","start","rootDir","getProjectRoot","config","getCliConfig","configPath","workDir","directory","legacyConfigPath","hasLegacyConfig","file","isFile","err","Error","code","fail","error","exit","typegen","warn","path","type","succeed","trace","telemetry","typegenConfig","typegenConfigMethod","result","onProgress","formatGeneratedCode","generates","schema","traceStats","log","configMethod","configOverloadClientMethods","overloadClientMethods","complete","isSpinning","String","message","resolve","promise","Promise","res","watchSpin","rendererContext","renderProgress","typegenWatcher","event","stop","process","off","step","getStats","on"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/typegen/generate.ts"],"sourcesContent":["import {stat} from 'node:fs/promises'\nimport {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {CLIError} from '@oclif/core/errors'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {spinner, type SpinnerInstance} from '@sanity/cli-core/ux'\nimport {\n configDefinition,\n readConfig,\n runTypegenGenerate,\n runTypegenWatcher,\n type TypeGenConfig,\n TypegenWatchModeTrace,\n TypesGeneratedTrace,\n} from '@sanity/codegen'\nimport omit from 'lodash-es/omit.js'\nimport once from 'lodash-es/once.js'\n\nimport {createTypegenProgressRenderer} from '../../actions/typegen/renderTypegenProgress.js'\n\nconst description = `Sanity TypeGen\n\n${styleText('bold', 'Configuration:')}\nThis command can utilize configuration settings defined in a \\`sanity-typegen.json\\` file. These settings include:\n\n- \"path\": Specifies a glob pattern to locate your TypeScript or JavaScript files.\n Default: \"./src/**/*.{ts,tsx,js,jsx}\"\n\n- \"schema\": Defines the path to your Sanity schema file. This file should be generated using the \\`sanity schema extract\\` command.\n Default: \"schema.json\"\n\n- \"generates\": Indicates the path where the generated TypeScript type definitions will be saved.\n Default: \"./sanity.types.ts\"\n\nThe default configuration values listed above are used if not overridden in your \\`sanity-typegen.json\\` configuration file. To customize the behavior of the type generation, adjust these properties in the configuration file according to your project's needs.\n\n${styleText('bold', 'Note:')}\n- The \\`sanity schema extract\\` command is a prerequisite for extracting your Sanity Studio schema into a \\`schema.json\\` file, which is then used by the \\`sanity typegen generate\\` command to generate type definitions.`.trim()\n\nexport class TypegenGenerateCommand extends SanityCommand<typeof TypegenGenerateCommand> {\n static override description = description\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: `Generate TypeScript type definitions from a Sanity Studio schema extracted using the \\`sanity schema extract\\` command.`,\n },\n ]\n\n static override flags = {\n 'config-path': Flags.string({\n description:\n '[Default: sanity-typegen.json] Specifies the path to the typegen configuration file. This file should be a JSON file that contains settings for the type generation process.',\n }),\n watch: Flags.boolean({\n default: false,\n description: '[Default: false] Run the typegen in watch mode',\n }),\n }\n\n static override hiddenAliases: string[] = ['typegen:generate']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(TypegenGenerateCommand)\n\n if (flags.watch) {\n await this.runWatcher()\n return\n }\n\n await this.runSingle()\n }\n\n private async getConfig(): Promise<{\n config: TypeGenConfig\n path?: string\n type: 'cli' | 'legacy'\n workDir: string\n }> {\n const spin = spinner({}).start('Loading config…')\n\n try {\n const {flags} = await this.parse(TypegenGenerateCommand)\n const rootDir = await this.getProjectRoot()\n const config = await this.getCliConfig()\n\n const configPath = flags['config-path']\n const workDir = rootDir.directory\n\n const legacyConfigPath = configPath || 'sanity-typegen.json'\n let hasLegacyConfig = false\n\n try {\n const file = await stat(legacyConfigPath)\n hasLegacyConfig = file.isFile()\n } catch (err) {\n if (err instanceof Error && 'code' in err && err.code === 'ENOENT' && configPath) {\n spin.fail()\n this.error(`Typegen config file not found: ${configPath}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n if (err instanceof Error && 'code' in err && err.code !== 'ENOENT') {\n spin.fail()\n this.error(`Error when checking if typegen config file exists: ${legacyConfigPath}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n\n if (config?.typegen && hasLegacyConfig) {\n spin.warn(\n styleText(\n 'yellow',\n `You've specified typegen in your Sanity CLI config, but also have a typegen config.\n\n The config from the Sanity CLI config is used.\n `,\n ),\n )\n\n return {\n config: configDefinition.parse(config.typegen || {}),\n path: rootDir.path,\n type: 'cli',\n workDir,\n }\n }\n\n if (hasLegacyConfig) {\n spin.warn(\n styleText(\n 'yellow',\n `The separate typegen config has been deprecated. Use \\`typegen\\` in the sanity CLI config instead.\n\n See: https://www.sanity.io/docs/help/configuring-typegen-in-sanity-cli-config`,\n ),\n )\n return {\n config: await readConfig(legacyConfigPath),\n path: legacyConfigPath,\n type: 'legacy',\n workDir,\n }\n }\n\n spin.succeed(`Config loaded from sanity.cli.ts`)\n\n return {\n config: configDefinition.parse(config.typegen || {}),\n path: rootDir.path,\n type: 'cli',\n workDir,\n }\n } catch (err) {\n // The config-file checks above already reported a user-facing error via\n // `this.error()` (an oclif CLIError). Re-throw those as-is instead of\n // wrapping them a second time and failing the spinner twice.\n if (err instanceof CLIError) throw err\n spin.fail()\n this.error(`An error occurred during config loading: ${err}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n\n private async runSingle(): Promise<void> {\n const trace = this.telemetry.trace(TypesGeneratedTrace)\n let spin: SpinnerInstance | undefined\n\n try {\n const {config: typegenConfig, type: typegenConfigMethod, workDir} = await this.getConfig()\n trace.start()\n\n spin = spinner({}).start('Loading schema…')\n const result = await runTypegenGenerate({\n config: typegenConfig,\n onProgress: createTypegenProgressRenderer(spin, {\n formatGeneratedCode: typegenConfig.formatGeneratedCode,\n generates: typegenConfig.generates,\n schema: typegenConfig.schema,\n }),\n workDir,\n })\n\n const traceStats = omit(result, 'code', 'duration')\n\n trace.log({\n configMethod: typegenConfigMethod,\n configOverloadClientMethods: typegenConfig.overloadClientMethods,\n ...traceStats,\n })\n trace.complete()\n } catch (error) {\n if (spin?.isSpinning) {\n spin.fail()\n }\n trace.error(error instanceof Error ? error : new Error(String(error)))\n this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n\n private async runWatcher(): Promise<void> {\n const trace = this.telemetry.trace(TypegenWatchModeTrace)\n let spin: SpinnerInstance | undefined\n\n try {\n const {config: typegenConfig, workDir} = await this.getConfig()\n trace.start()\n\n let resolve: () => void = () => {}\n const promise = new Promise<void>((res) => {\n resolve = res\n })\n\n const watchSpin = spinner({}).start('Loading schema…')\n spin = watchSpin\n const rendererContext = {\n formatGeneratedCode: typegenConfig.formatGeneratedCode,\n generates: typegenConfig.generates,\n schema: typegenConfig.schema,\n }\n let renderProgress = createTypegenProgressRenderer(watchSpin, rendererContext)\n\n const typegenWatcher = runTypegenWatcher({\n config: typegenConfig,\n onProgress: (event) => {\n // 'schemaLoaded' is the first event of every generation cycle; the\n // renderer accumulates per-run state, so start each cycle fresh.\n if (event.type === 'schemaLoaded') {\n renderProgress = createTypegenProgressRenderer(watchSpin, rendererContext)\n }\n renderProgress(event)\n },\n workDir,\n })\n\n const stop = once(async () => {\n process.off('SIGINT', stop)\n process.off('SIGTERM', stop)\n\n if (watchSpin.isSpinning) {\n watchSpin.stop()\n }\n\n trace.log({\n step: 'stopped',\n ...typegenWatcher.getStats(),\n })\n trace.complete()\n\n try {\n await typegenWatcher.stop()\n } catch {\n // We're shutting down; a failure to close the watcher shouldn't hang\n // the process or surface as an unhandled rejection.\n }\n resolve()\n })\n\n process.on('SIGINT', stop)\n process.on('SIGTERM', stop)\n\n await promise\n } catch (error) {\n if (spin?.isSpinning) {\n spin.fail()\n }\n trace.error(error instanceof Error ? error : new Error(String(error)))\n this.error(`${error instanceof Error ? error.message : 'Unknown error'}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n }\n}\n"],"names":["stat","styleText","Flags","CLIError","exitCodes","SanityCommand","spinner","configDefinition","readConfig","runTypegenGenerate","runTypegenWatcher","TypegenWatchModeTrace","TypesGeneratedTrace","omit","once","createTypegenProgressRenderer","description","trim","TypegenGenerateCommand","examples","command","flags","string","watch","boolean","default","hiddenAliases","run","parse","runWatcher","runSingle","getConfig","spin","start","rootDir","getProjectRoot","config","getCliConfig","configPath","workDir","directory","legacyConfigPath","hasLegacyConfig","file","isFile","err","Error","code","fail","error","exit","RUNTIME_ERROR","typegen","warn","path","type","succeed","trace","telemetry","typegenConfig","typegenConfigMethod","result","onProgress","formatGeneratedCode","generates","schema","traceStats","log","configMethod","configOverloadClientMethods","overloadClientMethods","complete","isSpinning","String","message","resolve","promise","Promise","res","watchSpin","rendererContext","renderProgress","typegenWatcher","event","stop","process","off","step","getStats","on"],"mappings":"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"}
|
|
@@ -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 = `${
|
|
62
|
-
|
|
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 = `${
|
|
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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"}
|
package/dist/util/appId.js
CHANGED
|
@@ -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:
|
|
37
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
// Just warn if only the old app ID config is found
|
package/dist/util/appId.js.map
CHANGED
|
@@ -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
|
+
{"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(
|
|
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'\
|
|
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"}
|