@sanity/cli 8.11.0 → 8.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -26
- package/dist/actions/assets/ingestAssetFromUrlWithProgress.js +21 -0
- package/dist/actions/assets/ingestAssetFromUrlWithProgress.js.map +1 -0
- package/dist/actions/assets/withUrlIngestProgress.js +45 -0
- package/dist/actions/assets/withUrlIngestProgress.js.map +1 -0
- package/dist/actions/auth/authServer.js +5 -8
- package/dist/actions/auth/authServer.js.map +1 -1
- package/dist/actions/deploy/deployApp.js +5 -2
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployChecks.js +3 -2
- package/dist/actions/deploy/deployChecks.js.map +1 -1
- package/dist/actions/deploy/findUserApplication.js +3 -2
- package/dist/actions/deploy/findUserApplication.js.map +1 -1
- package/dist/actions/deploy/resolveDeployTarget.js +6 -1
- package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
- package/dist/actions/documents/validateDocuments.worker.js +1 -4
- package/dist/actions/documents/validateDocuments.worker.js.map +1 -1
- package/dist/actions/media/importMedia.js.map +1 -1
- package/dist/actions/media/ingestMediaAssetFromUrlWithProgress.js +19 -0
- package/dist/actions/media/ingestMediaAssetFromUrlWithProgress.js.map +1 -0
- package/dist/commands/assets/upload.js +78 -52
- package/dist/commands/assets/upload.js.map +1 -1
- package/dist/commands/deploy.js +33 -1
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/media/import.js +149 -13
- package/dist/commands/media/import.js.map +1 -1
- package/dist/exports/invokeSanityCli/commandPolicies/index.js +1 -4
- package/dist/exports/invokeSanityCli/commandPolicies/index.js.map +1 -1
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +26 -6
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/exports/invokeSanityCli/commandPolicies/resolve.js +112 -0
- package/dist/exports/invokeSanityCli/commandPolicies/resolve.js.map +1 -0
- package/dist/exports/invokeSanityCli/help.js +8 -1
- package/dist/exports/invokeSanityCli/help.js.map +1 -1
- package/dist/exports/invokeSanityCli/index.d.ts +1 -2
- package/dist/exports/invokeSanityCli/index.js +37 -15
- package/dist/exports/invokeSanityCli/index.js.map +1 -1
- package/dist/generated/apiRoutes.js +2 -0
- package/dist/generated/apiRoutes.js.map +1 -1
- package/dist/services/assets.js +46 -1
- package/dist/services/assets.js.map +1 -1
- package/dist/services/mediaLibraries.js +39 -1
- package/dist/services/mediaLibraries.js.map +1 -1
- package/dist/util/assetSourceValidation.js +56 -0
- package/dist/util/assetSourceValidation.js.map +1 -0
- package/dist/util/assetUploadErrors.js +64 -0
- package/dist/util/assetUploadErrors.js.map +1 -0
- package/dist/util/isRemoteAssetSource.js +19 -0
- package/dist/util/isRemoteAssetSource.js.map +1 -0
- package/dist/util/parseAspectFlags.js +34 -0
- package/dist/util/parseAspectFlags.js.map +1 -0
- package/oclif.config.js +0 -1
- package/oclif.manifest.json +3071 -2992
- package/package.json +18 -18
- package/templates/app-quickstart/AGENTS.md +18 -0
- package/templates/app-sanity-ui/AGENTS.md +18 -0
- package/templates/shopify/.gitignore +26 -0
- package/dist/exports/invokeSanityCli/commandPolicies/policy.js +0 -46
- package/dist/exports/invokeSanityCli/commandPolicies/policy.js.map +0 -1
|
@@ -1,46 +1,15 @@
|
|
|
1
1
|
import { basename, resolve } from 'node:path';
|
|
2
2
|
import { Flags } from '@oclif/core';
|
|
3
3
|
import { exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
4
|
-
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
5
|
-
import { isHttpError } from '@sanity/client';
|
|
6
4
|
import { AssetFileError } from '../../actions/assets/assetFileError.js';
|
|
5
|
+
import { ingestAssetFromUrlWithProgress } from '../../actions/assets/ingestAssetFromUrlWithProgress.js';
|
|
7
6
|
import { uploadAssetWithProgress } from '../../actions/assets/uploadAssetWithProgress.js';
|
|
8
7
|
import { promptForProject } from '../../prompts/promptForProject.js';
|
|
8
|
+
import { getAssetFilenameError, getIngestUrlError } from '../../util/assetSourceValidation.js';
|
|
9
|
+
import { getAssetUploadErrorMessage } from '../../util/assetUploadErrors.js';
|
|
9
10
|
import { getDatasetFlag, getProjectIdFlag } from '../../util/sharedFlags.js';
|
|
10
11
|
import { defineCommandTelemetry } from '../../util/telemetry/commandTelemetry.js';
|
|
11
12
|
const uploadAssetDebug = subdebug('assets:upload');
|
|
12
|
-
const DATASET_ASSET_LIMITS_URL = 'https://www.sanity.io/docs/content-lake/technical-limits#k2c53dc30e24b';
|
|
13
|
-
function isProjectUserNotFoundError(body) {
|
|
14
|
-
const responseError = body.error;
|
|
15
|
-
return typeof responseError === 'object' && responseError !== null && 'type' in responseError && responseError.type === 'projectUserNotFoundError';
|
|
16
|
-
}
|
|
17
|
-
function getAssetUploadErrorMessage(error) {
|
|
18
|
-
if (!isHttpError(error)) {
|
|
19
|
-
return `Asset upload failed: ${getErrorMessage(error)}`;
|
|
20
|
-
}
|
|
21
|
-
const body = typeof error.response.body === 'object' && error.response.body !== null && !Array.isArray(error.response.body) ? error.response.body : {};
|
|
22
|
-
const responseError = typeof body.error === 'string' ? body.error : error.response.statusMessage || 'HTTP error';
|
|
23
|
-
const statusCode = typeof body.statusCode === 'number' || typeof body.statusCode === 'string' ? body.statusCode : error.statusCode;
|
|
24
|
-
const projectUserNotFound = isProjectUserNotFoundError(body);
|
|
25
|
-
const responseMessage = projectUserNotFound ? error.message : getErrorMessage(error);
|
|
26
|
-
const message = /[.!?]$/.test(responseMessage) ? responseMessage : `${responseMessage}.`;
|
|
27
|
-
const details = typeof body.details === 'string' ? `\n\nDetails:\n${body.details}` : '';
|
|
28
|
-
const response = `Asset upload failed: HTTP ${statusCode} - ${responseError}\n${message}${details}`;
|
|
29
|
-
if (error.statusCode === 401 && !projectUserNotFound) {
|
|
30
|
-
return `${response}\n\nRun \`sanity login\` to authenticate, then try again.`;
|
|
31
|
-
}
|
|
32
|
-
if (error.statusCode === 403) {
|
|
33
|
-
return `${response}\n\nCheck that your account has write access to this dataset, then try again.`;
|
|
34
|
-
}
|
|
35
|
-
if ([
|
|
36
|
-
400,
|
|
37
|
-
413,
|
|
38
|
-
422
|
|
39
|
-
].includes(error.statusCode)) {
|
|
40
|
-
return `${response}\n\nCheck the asset requirements and current technical limits, then try again: ${DATASET_ASSET_LIMITS_URL}`;
|
|
41
|
-
}
|
|
42
|
-
return `${response}\n\nTry again.`;
|
|
43
|
-
}
|
|
44
13
|
const flags = {
|
|
45
14
|
...getProjectIdFlag({
|
|
46
15
|
description: 'Project ID to upload the asset to',
|
|
@@ -51,18 +20,29 @@ const flags = {
|
|
|
51
20
|
semantics: 'override'
|
|
52
21
|
}),
|
|
53
22
|
'content-type': Flags.string({
|
|
54
|
-
description: 'MIME type of the asset, such as image/png or application/pdf',
|
|
23
|
+
description: 'MIME type of the asset, such as image/png or application/pdf. Only applies to --file',
|
|
55
24
|
helpValue: '<mime-type>'
|
|
56
25
|
}),
|
|
57
26
|
file: Flags.string({
|
|
58
27
|
description: 'Path to the local file to upload',
|
|
59
|
-
|
|
60
|
-
|
|
28
|
+
exactlyOne: [
|
|
29
|
+
'file',
|
|
30
|
+
'from-url'
|
|
31
|
+
],
|
|
32
|
+
helpValue: '<path>'
|
|
61
33
|
}),
|
|
62
34
|
filename: Flags.string({
|
|
63
|
-
description: 'Original filename stored on the asset document. Defaults to the local filename',
|
|
35
|
+
description: 'Original filename stored on the asset document. Defaults to the local filename when using --file',
|
|
64
36
|
helpValue: '<filename>'
|
|
65
37
|
}),
|
|
38
|
+
'from-url': Flags.string({
|
|
39
|
+
description: 'https URL for Sanity to fetch the asset from, instead of uploading a local file. Must be reachable from the public internet without authentication',
|
|
40
|
+
exactlyOne: [
|
|
41
|
+
'file',
|
|
42
|
+
'from-url'
|
|
43
|
+
],
|
|
44
|
+
helpValue: '<url>'
|
|
45
|
+
}),
|
|
66
46
|
type: Flags.custom({
|
|
67
47
|
default: 'image',
|
|
68
48
|
description: 'Asset type to create',
|
|
@@ -73,7 +53,7 @@ const flags = {
|
|
|
73
53
|
})()
|
|
74
54
|
};
|
|
75
55
|
export class UploadAssetCommand extends SanityCommand {
|
|
76
|
-
static description = 'Upload one
|
|
56
|
+
static description = 'Upload one image or file to a Sanity dataset, from a local path or a URL, and print the asset document as JSON';
|
|
77
57
|
static examples = [
|
|
78
58
|
{
|
|
79
59
|
command: '<%= config.bin %> <%= command.id %> --file ./hero.png --type image --dataset production',
|
|
@@ -82,6 +62,10 @@ export class UploadAssetCommand extends SanityCommand {
|
|
|
82
62
|
{
|
|
83
63
|
command: '<%= config.bin %> <%= command.id %> --file ./brief.pdf --type file --content-type application/pdf --project-id abc123 --dataset production',
|
|
84
64
|
description: 'Upload a file with explicit project, dataset, and MIME type'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
command: "<%= config.bin %> <%= command.id %> --from-url 'https://example.com/hero.png' --type image --dataset production",
|
|
68
|
+
description: 'Have Sanity fetch an image from a public URL'
|
|
85
69
|
}
|
|
86
70
|
];
|
|
87
71
|
static flags = flags;
|
|
@@ -91,12 +75,30 @@ export class UploadAssetCommand extends SanityCommand {
|
|
|
91
75
|
static telemetry = defineCommandTelemetry(flags, {
|
|
92
76
|
redact: [
|
|
93
77
|
'file',
|
|
94
|
-
'filename'
|
|
78
|
+
'filename',
|
|
79
|
+
'from-url'
|
|
95
80
|
]
|
|
96
81
|
});
|
|
97
82
|
async run() {
|
|
98
83
|
const { flags } = await this.parse(UploadAssetCommand);
|
|
99
|
-
const
|
|
84
|
+
const sourceUrl = flags['from-url'];
|
|
85
|
+
if (sourceUrl && flags['content-type']) {
|
|
86
|
+
this.error('Asset upload failed: --content-type cannot be combined with --from-url. Sanity derives the MIME type automatically.', {
|
|
87
|
+
exit: exitCodes.USAGE_ERROR
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const sourceUrlError = sourceUrl === undefined ? undefined : getIngestUrlError(sourceUrl);
|
|
91
|
+
if (sourceUrlError) {
|
|
92
|
+
this.error(`Asset upload failed: ${sourceUrlError}`, {
|
|
93
|
+
exit: exitCodes.USAGE_ERROR
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const filenameError = flags.filename === undefined ? undefined : getAssetFilenameError(flags.filename);
|
|
97
|
+
if (filenameError) {
|
|
98
|
+
this.error(`Asset upload failed: ${filenameError}`, {
|
|
99
|
+
exit: exitCodes.USAGE_ERROR
|
|
100
|
+
});
|
|
101
|
+
}
|
|
100
102
|
const cliConfig = await this.tryGetCliConfig();
|
|
101
103
|
const projectId = await this.getProjectId({
|
|
102
104
|
fallback: ()=>promptForProject({})
|
|
@@ -107,17 +109,38 @@ export class UploadAssetCommand extends SanityCommand {
|
|
|
107
109
|
exit: exitCodes.USAGE_ERROR
|
|
108
110
|
});
|
|
109
111
|
}
|
|
112
|
+
const isInteractive = this.resolveIsInteractive();
|
|
110
113
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
let asset;
|
|
115
|
+
if (sourceUrl === undefined) {
|
|
116
|
+
// `exactlyOne` on the flags guarantees a source, but not to the compiler.
|
|
117
|
+
if (flags.file === undefined) {
|
|
118
|
+
this.error('Asset upload failed: Pass either --file <path> or --from-url <url>.', {
|
|
119
|
+
exit: exitCodes.USAGE_ERROR
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const filePath = resolve(flags.file);
|
|
123
|
+
asset = await uploadAssetWithProgress({
|
|
124
|
+
assetType: flags.type,
|
|
125
|
+
contentType: flags['content-type'],
|
|
126
|
+
dataset,
|
|
127
|
+
filename: flags.filename ?? basename(filePath),
|
|
128
|
+
filePath,
|
|
129
|
+
isInteractive,
|
|
130
|
+
logToStderr: (message)=>this.logToStderr(message),
|
|
131
|
+
projectId
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
asset = await ingestAssetFromUrlWithProgress({
|
|
135
|
+
assetType: flags.type,
|
|
136
|
+
dataset,
|
|
137
|
+
filename: flags.filename,
|
|
138
|
+
isInteractive,
|
|
139
|
+
logToStderr: (message)=>this.logToStderr(message),
|
|
140
|
+
projectId,
|
|
141
|
+
url: sourceUrl
|
|
142
|
+
});
|
|
143
|
+
}
|
|
121
144
|
const fieldType = flags.type === 'image' ? 'image' : 'file';
|
|
122
145
|
this.log(JSON.stringify({
|
|
123
146
|
asset: {
|
|
@@ -150,7 +173,10 @@ export class UploadAssetCommand extends SanityCommand {
|
|
|
150
173
|
});
|
|
151
174
|
}
|
|
152
175
|
uploadAssetDebug('Asset upload failed', error);
|
|
153
|
-
this.error(getAssetUploadErrorMessage(error
|
|
176
|
+
this.error(getAssetUploadErrorMessage(error, {
|
|
177
|
+
fromUrl: sourceUrl !== undefined,
|
|
178
|
+
target: 'dataset'
|
|
179
|
+
}), {
|
|
154
180
|
exit: exitCodes.RUNTIME_ERROR
|
|
155
181
|
});
|
|
156
182
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/assets/upload.ts"],"sourcesContent":["import {basename, resolve} from 'node:path'\n\nimport {Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {isHttpError} from '@sanity/client'\n\nimport {AssetFileError} from '../../actions/assets/assetFileError.js'\nimport {uploadAssetWithProgress} from '../../actions/assets/uploadAssetWithProgress.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {type AssetType} from '../../services/assets.js'\nimport {getDatasetFlag, getProjectIdFlag} from '../../util/sharedFlags.js'\nimport {defineCommandTelemetry} from '../../util/telemetry/commandTelemetry.js'\n\nconst uploadAssetDebug = subdebug('assets:upload')\nconst DATASET_ASSET_LIMITS_URL =\n 'https://www.sanity.io/docs/content-lake/technical-limits#k2c53dc30e24b'\n\nfunction isProjectUserNotFoundError(body: Record<string, unknown>): boolean {\n const responseError = body.error\n return (\n typeof responseError === 'object' &&\n responseError !== null &&\n 'type' in responseError &&\n responseError.type === 'projectUserNotFoundError'\n )\n}\n\nfunction getAssetUploadErrorMessage(error: unknown): string {\n if (!isHttpError(error)) {\n return `Asset upload failed: ${getErrorMessage(error)}`\n }\n\n const body =\n typeof error.response.body === 'object' &&\n error.response.body !== null &&\n !Array.isArray(error.response.body)\n ? (error.response.body as Record<string, unknown>)\n : {}\n const responseError =\n typeof body.error === 'string' ? body.error : error.response.statusMessage || 'HTTP error'\n const statusCode =\n typeof body.statusCode === 'number' || typeof body.statusCode === 'string'\n ? body.statusCode\n : error.statusCode\n const projectUserNotFound = isProjectUserNotFoundError(body)\n const responseMessage = projectUserNotFound ? error.message : getErrorMessage(error)\n const message = /[.!?]$/.test(responseMessage) ? responseMessage : `${responseMessage}.`\n const details = typeof body.details === 'string' ? `\\n\\nDetails:\\n${body.details}` : ''\n const response = `Asset upload failed: HTTP ${statusCode} - ${responseError}\\n${message}${details}`\n\n if (error.statusCode === 401 && !projectUserNotFound) {\n return `${response}\\n\\nRun \\`sanity login\\` to authenticate, then try again.`\n }\n if (error.statusCode === 403) {\n return `${response}\\n\\nCheck that your account has write access to this dataset, then try again.`\n }\n if ([400, 413, 422].includes(error.statusCode)) {\n return `${response}\\n\\nCheck the asset requirements and current technical limits, then try again: ${DATASET_ASSET_LIMITS_URL}`\n }\n return `${response}\\n\\nTry again.`\n}\n\nconst flags = {\n ...getProjectIdFlag({\n description: 'Project ID to upload the asset to',\n semantics: 'override',\n }),\n ...getDatasetFlag({description: 'Dataset to upload the asset to', semantics: 'override'}),\n 'content-type': Flags.string({\n description: 'MIME type of the asset, such as image/png or application/pdf',\n helpValue: '<mime-type>',\n }),\n file: Flags.string({\n description: 'Path to the local file to upload',\n helpValue: '<path>',\n required: true,\n }),\n filename: Flags.string({\n description: 'Original filename stored on the asset document. Defaults to the local filename',\n helpValue: '<filename>',\n }),\n type: Flags.custom<AssetType>({\n default: 'image',\n description: 'Asset type to create',\n options: ['image', 'file'],\n })(),\n} satisfies FlagInput\n\nexport class UploadAssetCommand extends SanityCommand<typeof UploadAssetCommand> {\n static override description =\n 'Upload one local image or file to a Sanity dataset and print the asset document as JSON'\n\n static override examples = [\n {\n command:\n '<%= config.bin %> <%= command.id %> --file ./hero.png --type image --dataset production',\n description: 'Upload an image using the configured project',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> --file ./brief.pdf --type file --content-type application/pdf --project-id abc123 --dataset production',\n description: 'Upload a file with explicit project, dataset, and MIME type',\n },\n ]\n\n static override flags = flags\n\n static override hiddenAliases: string[] = ['asset:upload']\n\n static telemetry = defineCommandTelemetry(flags, {\n redact: ['file', 'filename'],\n })\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(UploadAssetCommand)\n const filePath = resolve(flags.file)\n\n const cliConfig = await this.tryGetCliConfig()\n const projectId = await this.getProjectId({fallback: () => promptForProject({})})\n const dataset = flags.dataset ?? cliConfig.api?.dataset\n if (!dataset) {\n this.error(\n 'Asset upload failed: Dataset is required. Pass --dataset <name> or configure a dataset in sanity.cli.ts.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n\n try {\n const asset = await uploadAssetWithProgress({\n assetType: flags.type,\n contentType: flags['content-type'],\n dataset,\n filename: flags.filename ?? basename(filePath),\n filePath,\n isInteractive: this.resolveIsInteractive(),\n logToStderr: (message) => this.logToStderr(message),\n projectId,\n })\n const fieldType = flags.type === 'image' ? 'image' : 'file'\n\n this.log(\n JSON.stringify(\n {\n asset: {\n _id: asset._id,\n _type: asset._type,\n extension: asset.extension,\n mimeType: asset.mimeType,\n originalFilename: asset.originalFilename,\n size: asset.size,\n url: asset.url,\n },\n reference: {\n _type: fieldType,\n asset: {_ref: asset._id, _type: 'reference'},\n },\n },\n null,\n 2,\n ),\n )\n } catch (error) {\n if (error instanceof Error && error.message === 'SIGINT') throw error\n if (error instanceof AssetFileError) {\n if (error.reason === 'not-file') {\n this.error('Asset upload failed: --file must point to a file, not a directory.', {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n this.error(\n 'Asset upload failed: Cannot read the local file. Check that --file points to a readable file, then retry.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n uploadAssetDebug('Asset upload failed', error)\n this.error(getAssetUploadErrorMessage(error), {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n}\n"],"names":["basename","resolve","Flags","exitCodes","SanityCommand","subdebug","getErrorMessage","isHttpError","AssetFileError","uploadAssetWithProgress","promptForProject","getDatasetFlag","getProjectIdFlag","defineCommandTelemetry","uploadAssetDebug","DATASET_ASSET_LIMITS_URL","isProjectUserNotFoundError","body","responseError","error","type","getAssetUploadErrorMessage","response","Array","isArray","statusMessage","statusCode","projectUserNotFound","responseMessage","message","test","details","includes","flags","description","semantics","string","helpValue","file","required","filename","custom","default","options","UploadAssetCommand","examples","command","hiddenAliases","telemetry","redact","run","parse","filePath","cliConfig","tryGetCliConfig","projectId","getProjectId","fallback","dataset","api","exit","USAGE_ERROR","asset","assetType","contentType","isInteractive","resolveIsInteractive","logToStderr","fieldType","log","JSON","stringify","_id","_type","extension","mimeType","originalFilename","size","url","reference","_ref","Error","reason","RUNTIME_ERROR"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/assets/upload.ts"],"sourcesContent":["import {basename, resolve} from 'node:path'\n\nimport {Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\n\nimport {AssetFileError} from '../../actions/assets/assetFileError.js'\nimport {ingestAssetFromUrlWithProgress} from '../../actions/assets/ingestAssetFromUrlWithProgress.js'\nimport {uploadAssetWithProgress} from '../../actions/assets/uploadAssetWithProgress.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {type AssetType} from '../../services/assets.js'\nimport {getAssetFilenameError, getIngestUrlError} from '../../util/assetSourceValidation.js'\nimport {getAssetUploadErrorMessage} from '../../util/assetUploadErrors.js'\nimport {getDatasetFlag, getProjectIdFlag} from '../../util/sharedFlags.js'\nimport {defineCommandTelemetry} from '../../util/telemetry/commandTelemetry.js'\n\nconst uploadAssetDebug = subdebug('assets:upload')\n\nconst flags = {\n ...getProjectIdFlag({\n description: 'Project ID to upload the asset to',\n semantics: 'override',\n }),\n ...getDatasetFlag({description: 'Dataset to upload the asset to', semantics: 'override'}),\n 'content-type': Flags.string({\n description:\n 'MIME type of the asset, such as image/png or application/pdf. Only applies to --file',\n helpValue: '<mime-type>',\n }),\n file: Flags.string({\n description: 'Path to the local file to upload',\n exactlyOne: ['file', 'from-url'],\n helpValue: '<path>',\n }),\n filename: Flags.string({\n description:\n 'Original filename stored on the asset document. Defaults to the local filename when using --file',\n helpValue: '<filename>',\n }),\n 'from-url': Flags.string({\n description:\n 'https URL for Sanity to fetch the asset from, instead of uploading a local file. Must be reachable from the public internet without authentication',\n exactlyOne: ['file', 'from-url'],\n helpValue: '<url>',\n }),\n type: Flags.custom<AssetType>({\n default: 'image',\n description: 'Asset type to create',\n options: ['image', 'file'],\n })(),\n} satisfies FlagInput\n\nexport class UploadAssetCommand extends SanityCommand<typeof UploadAssetCommand> {\n static override description =\n 'Upload one image or file to a Sanity dataset, from a local path or a URL, and print the asset document as JSON'\n\n static override examples = [\n {\n command:\n '<%= config.bin %> <%= command.id %> --file ./hero.png --type image --dataset production',\n description: 'Upload an image using the configured project',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> --file ./brief.pdf --type file --content-type application/pdf --project-id abc123 --dataset production',\n description: 'Upload a file with explicit project, dataset, and MIME type',\n },\n {\n command:\n \"<%= config.bin %> <%= command.id %> --from-url 'https://example.com/hero.png' --type image --dataset production\",\n description: 'Have Sanity fetch an image from a public URL',\n },\n ]\n\n static override flags = flags\n\n static override hiddenAliases: string[] = ['asset:upload']\n\n static telemetry = defineCommandTelemetry(flags, {\n redact: ['file', 'filename', 'from-url'],\n })\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(UploadAssetCommand)\n const sourceUrl = flags['from-url']\n\n if (sourceUrl && flags['content-type']) {\n this.error(\n 'Asset upload failed: --content-type cannot be combined with --from-url. Sanity derives the MIME type automatically.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n\n const sourceUrlError = sourceUrl === undefined ? undefined : getIngestUrlError(sourceUrl)\n if (sourceUrlError) {\n this.error(`Asset upload failed: ${sourceUrlError}`, {exit: exitCodes.USAGE_ERROR})\n }\n\n const filenameError =\n flags.filename === undefined ? undefined : getAssetFilenameError(flags.filename)\n if (filenameError) {\n this.error(`Asset upload failed: ${filenameError}`, {exit: exitCodes.USAGE_ERROR})\n }\n\n const cliConfig = await this.tryGetCliConfig()\n const projectId = await this.getProjectId({fallback: () => promptForProject({})})\n const dataset = flags.dataset ?? cliConfig.api?.dataset\n if (!dataset) {\n this.error(\n 'Asset upload failed: Dataset is required. Pass --dataset <name> or configure a dataset in sanity.cli.ts.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n\n const isInteractive = this.resolveIsInteractive()\n\n try {\n let asset\n if (sourceUrl === undefined) {\n // `exactlyOne` on the flags guarantees a source, but not to the compiler.\n if (flags.file === undefined) {\n this.error('Asset upload failed: Pass either --file <path> or --from-url <url>.', {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n const filePath = resolve(flags.file)\n asset = await uploadAssetWithProgress({\n assetType: flags.type,\n contentType: flags['content-type'],\n dataset,\n filename: flags.filename ?? basename(filePath),\n filePath,\n isInteractive,\n logToStderr: (message) => this.logToStderr(message),\n projectId,\n })\n } else {\n asset = await ingestAssetFromUrlWithProgress({\n assetType: flags.type,\n dataset,\n filename: flags.filename,\n isInteractive,\n logToStderr: (message) => this.logToStderr(message),\n projectId,\n url: sourceUrl,\n })\n }\n const fieldType = flags.type === 'image' ? 'image' : 'file'\n\n this.log(\n JSON.stringify(\n {\n asset: {\n _id: asset._id,\n _type: asset._type,\n extension: asset.extension,\n mimeType: asset.mimeType,\n originalFilename: asset.originalFilename,\n size: asset.size,\n url: asset.url,\n },\n reference: {\n _type: fieldType,\n asset: {_ref: asset._id, _type: 'reference'},\n },\n },\n null,\n 2,\n ),\n )\n } catch (error) {\n if (error instanceof Error && error.message === 'SIGINT') throw error\n if (error instanceof AssetFileError) {\n if (error.reason === 'not-file') {\n this.error('Asset upload failed: --file must point to a file, not a directory.', {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n this.error(\n 'Asset upload failed: Cannot read the local file. Check that --file points to a readable file, then retry.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n uploadAssetDebug('Asset upload failed', error)\n this.error(\n getAssetUploadErrorMessage(error, {\n fromUrl: sourceUrl !== undefined,\n target: 'dataset',\n }),\n {exit: exitCodes.RUNTIME_ERROR},\n )\n }\n }\n}\n"],"names":["basename","resolve","Flags","exitCodes","SanityCommand","subdebug","AssetFileError","ingestAssetFromUrlWithProgress","uploadAssetWithProgress","promptForProject","getAssetFilenameError","getIngestUrlError","getAssetUploadErrorMessage","getDatasetFlag","getProjectIdFlag","defineCommandTelemetry","uploadAssetDebug","flags","description","semantics","string","helpValue","file","exactlyOne","filename","type","custom","default","options","UploadAssetCommand","examples","command","hiddenAliases","telemetry","redact","run","parse","sourceUrl","error","exit","USAGE_ERROR","sourceUrlError","undefined","filenameError","cliConfig","tryGetCliConfig","projectId","getProjectId","fallback","dataset","api","isInteractive","resolveIsInteractive","asset","filePath","assetType","contentType","logToStderr","message","url","fieldType","log","JSON","stringify","_id","_type","extension","mimeType","originalFilename","size","reference","_ref","Error","reason","fromUrl","target","RUNTIME_ERROR"],"mappings":"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"}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -2,9 +2,11 @@ import path from 'node:path';
|
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { exitCodes, SanityCommand } from '@sanity/cli-core';
|
|
4
4
|
import { confirm } from '@sanity/cli-core/ux';
|
|
5
|
+
import { getWorkbench, resolveWorkbenchConfig } from '@sanity/workbench-cli/deploy';
|
|
5
6
|
import { deployApp } from '../actions/deploy/deployApp.js';
|
|
6
7
|
import { deployDebug } from '../actions/deploy/deployDebug.js';
|
|
7
8
|
import { deployStudio } from '../actions/deploy/deployStudio.js';
|
|
9
|
+
import { getAppId } from '../util/appId.js';
|
|
8
10
|
import { determineIsApp } from '../util/determineIsApp.js';
|
|
9
11
|
import { dirIsEmptyOrNonExistent } from '../util/dirIsEmptyOrNonExistent.js';
|
|
10
12
|
export class DeployCommand extends SanityCommand {
|
|
@@ -30,6 +32,18 @@ export class DeployCommand extends SanityCommand {
|
|
|
30
32
|
{
|
|
31
33
|
command: '<%= config.bin %> <%= command.id %> --external',
|
|
32
34
|
description: 'Register an externally hosted studio (studioHost contains full URL)'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
command: '<%= config.bin %> <%= command.id %> --create --title "My App" --yes --json',
|
|
38
|
+
description: 'Create and deploy an App SDK app without prompts. Save application.id as deployment.appId, then omit --create on later deploys'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
command: '<%= config.bin %> <%= command.id %> --create --title "My App" --dry-run --json',
|
|
42
|
+
description: 'Preview a new App SDK deployment without creating or uploading anything'
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
command: '<%= config.bin %> <%= command.id %> --url my-studio --yes',
|
|
46
|
+
description: 'Deploy a studio without prompts using its hostname'
|
|
33
47
|
}
|
|
34
48
|
];
|
|
35
49
|
static flags = {
|
|
@@ -43,6 +57,13 @@ export class DeployCommand extends SanityCommand {
|
|
|
43
57
|
default: true,
|
|
44
58
|
description: 'Build the studio before deploying (use --no-build to deploy existing `dist/` output)'
|
|
45
59
|
}),
|
|
60
|
+
create: Flags.boolean({
|
|
61
|
+
description: 'Create a new App SDK app, even if the organization has other apps',
|
|
62
|
+
exclusive: [
|
|
63
|
+
'external',
|
|
64
|
+
'url'
|
|
65
|
+
]
|
|
66
|
+
}),
|
|
46
67
|
'dry-run': Flags.boolean({
|
|
47
68
|
default: false,
|
|
48
69
|
description: 'Report what would be deployed without uploading or creating anything'
|
|
@@ -75,7 +96,7 @@ export class DeployCommand extends SanityCommand {
|
|
|
75
96
|
description: 'Enable source maps for built bundles (increases size of bundle)'
|
|
76
97
|
}),
|
|
77
98
|
title: Flags.string({
|
|
78
|
-
description: 'Title for a newly created application or studio. For apps
|
|
99
|
+
description: 'Title for a newly created application or studio. For apps, use --create to select creation when the organization already has apps'
|
|
79
100
|
}),
|
|
80
101
|
url: Flags.string({
|
|
81
102
|
description: 'Studio URL for deployment. For external studios, the full URL. For hosted studios, the hostname (e.g. "my-studio" or "my-studio.sanity.studio")'
|
|
@@ -95,6 +116,17 @@ export class DeployCommand extends SanityCommand {
|
|
|
95
116
|
const cliConfig = await this.getCliConfig();
|
|
96
117
|
const projectRoot = await this.getProjectRoot();
|
|
97
118
|
const isApp = determineIsApp(cliConfig);
|
|
119
|
+
if (flags.create) {
|
|
120
|
+
if (!isApp) {
|
|
121
|
+
return this.output.error('--create is only supported for App SDK apps. For a studio, use --url <hostname>.');
|
|
122
|
+
}
|
|
123
|
+
if (getAppId(cliConfig)) {
|
|
124
|
+
return this.output.error('--create cannot be used with a configured app ID. Omit --create to deploy to that app, or remove deployment.appId and app.id to create a new one.');
|
|
125
|
+
}
|
|
126
|
+
if (getWorkbench(cliConfig) || resolveWorkbenchConfig(cliConfig)) {
|
|
127
|
+
return this.output.error('--create is not supported for this app configuration. Omit --create and run sanity deploy.');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
98
130
|
const defaultOutputDir = path.resolve(path.join(projectRoot.directory, 'dist'));
|
|
99
131
|
const sourceDir = path.resolve(process.cwd(), this.args.sourceDir || defaultOutputDir);
|
|
100
132
|
// Skip the directory check if the studio is externally hosted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {confirm} from '@sanity/cli-core/ux'\n\nimport {deployApp} from '../actions/deploy/deployApp.js'\nimport {deployDebug} from '../actions/deploy/deployDebug.js'\nimport {deployStudio} from '../actions/deploy/deployStudio.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\nimport {dirIsEmptyOrNonExistent} from '../util/dirIsEmptyOrNonExistent.js'\n\nexport class DeployCommand extends SanityCommand<typeof DeployCommand> {\n static override args = {\n sourceDir: Args.directory({\n description: 'Source directory',\n }),\n }\n\n static override description = 'Builds and deploys Sanity Studio or application to Sanity hosting'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Build and deploy the studio to Sanity hosting',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-minify --source-maps',\n description: 'Deploys non-minified build with source maps',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --schema-required',\n description:\n 'Fail fast on schema store fails - for when other services rely on the stored schema',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --external',\n description: 'Register an externally hosted studio (studioHost contains full URL)',\n },\n ]\n\n static override flags = {\n 'auto-updates': Flags.boolean({\n allowNo: true,\n deprecated: true,\n description: 'Automatically update the studio to the latest version',\n }),\n build: Flags.boolean({\n allowNo: true,\n default: true,\n description:\n 'Build the studio before deploying (use --no-build to deploy existing `dist/` output)',\n }),\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be deployed without uploading or creating anything',\n }),\n external: Flags.boolean({\n default: false,\n description: 'Register an externally hosted studio',\n exclusive: ['source-maps', 'minify', 'build'],\n }),\n json: Flags.boolean({\n char: 'j',\n default: false,\n description: 'Output the result as JSON',\n }),\n minify: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Minify built JavaScript (use --no-minify to skip for faster builds)',\n }),\n 'schema-required': Flags.boolean({\n default: false,\n description: 'Fail if schema deployment fails',\n }),\n 'source-maps': Flags.boolean({\n default: false,\n description: 'Enable source maps for built bundles (increases size of bundle)',\n }),\n title: Flags.string({\n description:\n 'Title for a newly created application or studio. For apps it also skips the interactive title prompt, enabling unattended creation',\n }),\n url: Flags.string({\n description:\n 'Studio URL for deployment. For external studios, the full URL. For hosted studios, the hostname (e.g. \"my-studio\" or \"my-studio.sanity.studio\")',\n }),\n verbose: Flags.boolean({\n default: false,\n description: 'Enable verbose logging',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(DeployCommand)\n\n const cliConfig = await this.getCliConfig()\n const projectRoot = await this.getProjectRoot()\n\n const isApp = determineIsApp(cliConfig)\n\n const defaultOutputDir = path.resolve(path.join(projectRoot.directory, 'dist'))\n const sourceDir = path.resolve(process.cwd(), this.args.sourceDir || defaultOutputDir)\n\n // Skip the directory check if the studio is externally hosted\n if (this.args.sourceDir && this.args.sourceDir !== 'dist' && !flags.external) {\n let relativeOutput = path.relative(process.cwd(), sourceDir)\n if (relativeOutput[0] !== '.') {\n relativeOutput = `./${relativeOutput}`\n }\n\n if (!this.isUnattended() && !flags['dry-run']) {\n const isEmpty = await dirIsEmptyOrNonExistent(sourceDir)\n const shouldProceed =\n isEmpty ||\n (await confirm({\n default: false,\n message: `\"${relativeOutput}\" is not empty, do you want to proceed?`,\n }))\n\n if (!shouldProceed) {\n this.output.error('Cancelled.', {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n\n // Keep --json's stdout clean for the payload\n if (!flags.json) this.output.log(`Building to ${relativeOutput}\\n`)\n }\n\n // Force yes downstream: build/app resolution otherwise prompts for prerelease/version choices\n const deployFlags = this.isUnattended() || flags['dry-run'] ? {...flags, yes: true} : flags\n\n if (isApp) {\n deployDebug('Deploying app')\n await deployApp({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n } else {\n deployDebug('Deploying studio')\n await deployStudio({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n }\n }\n}\n"],"names":["path","Args","Flags","exitCodes","SanityCommand","confirm","deployApp","deployDebug","deployStudio","determineIsApp","dirIsEmptyOrNonExistent","DeployCommand","args","sourceDir","directory","description","examples","command","flags","boolean","allowNo","deprecated","build","default","external","exclusive","json","char","minify","title","string","url","verbose","yes","run","parse","cliConfig","getCliConfig","projectRoot","getProjectRoot","isApp","defaultOutputDir","resolve","join","process","cwd","relativeOutput","relative","isUnattended","isEmpty","shouldProceed","message","output","error","exit","RUNTIME_ERROR","log","deployFlags"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {confirm} from '@sanity/cli-core/ux'\nimport {getWorkbench, resolveWorkbenchConfig} from '@sanity/workbench-cli/deploy'\n\nimport {deployApp} from '../actions/deploy/deployApp.js'\nimport {deployDebug} from '../actions/deploy/deployDebug.js'\nimport {deployStudio} from '../actions/deploy/deployStudio.js'\nimport {getAppId} from '../util/appId.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\nimport {dirIsEmptyOrNonExistent} from '../util/dirIsEmptyOrNonExistent.js'\n\nexport class DeployCommand extends SanityCommand<typeof DeployCommand> {\n static override args = {\n sourceDir: Args.directory({\n description: 'Source directory',\n }),\n }\n\n static override description = 'Builds and deploys Sanity Studio or application to Sanity hosting'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Build and deploy the studio to Sanity hosting',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-minify --source-maps',\n description: 'Deploys non-minified build with source maps',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --schema-required',\n description:\n 'Fail fast on schema store fails - for when other services rely on the stored schema',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --external',\n description: 'Register an externally hosted studio (studioHost contains full URL)',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --create --title \"My App\" --yes --json',\n description:\n 'Create and deploy an App SDK app without prompts. Save application.id as deployment.appId, then omit --create on later deploys',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --create --title \"My App\" --dry-run --json',\n description: 'Preview a new App SDK deployment without creating or uploading anything',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --url my-studio --yes',\n description: 'Deploy a studio without prompts using its hostname',\n },\n ]\n\n static override flags = {\n 'auto-updates': Flags.boolean({\n allowNo: true,\n deprecated: true,\n description: 'Automatically update the studio to the latest version',\n }),\n build: Flags.boolean({\n allowNo: true,\n default: true,\n description:\n 'Build the studio before deploying (use --no-build to deploy existing `dist/` output)',\n }),\n create: Flags.boolean({\n description: 'Create a new App SDK app, even if the organization has other apps',\n exclusive: ['external', 'url'],\n }),\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be deployed without uploading or creating anything',\n }),\n external: Flags.boolean({\n default: false,\n description: 'Register an externally hosted studio',\n exclusive: ['source-maps', 'minify', 'build'],\n }),\n json: Flags.boolean({\n char: 'j',\n default: false,\n description: 'Output the result as JSON',\n }),\n minify: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Minify built JavaScript (use --no-minify to skip for faster builds)',\n }),\n 'schema-required': Flags.boolean({\n default: false,\n description: 'Fail if schema deployment fails',\n }),\n 'source-maps': Flags.boolean({\n default: false,\n description: 'Enable source maps for built bundles (increases size of bundle)',\n }),\n title: Flags.string({\n description:\n 'Title for a newly created application or studio. For apps, use --create to select creation when the organization already has apps',\n }),\n url: Flags.string({\n description:\n 'Studio URL for deployment. For external studios, the full URL. For hosted studios, the hostname (e.g. \"my-studio\" or \"my-studio.sanity.studio\")',\n }),\n verbose: Flags.boolean({\n default: false,\n description: 'Enable verbose logging',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(DeployCommand)\n\n const cliConfig = await this.getCliConfig()\n const projectRoot = await this.getProjectRoot()\n\n const isApp = determineIsApp(cliConfig)\n\n if (flags.create) {\n if (!isApp) {\n return this.output.error(\n '--create is only supported for App SDK apps. For a studio, use --url <hostname>.',\n )\n }\n if (getAppId(cliConfig)) {\n return this.output.error(\n '--create cannot be used with a configured app ID. Omit --create to deploy to that app, or remove deployment.appId and app.id to create a new one.',\n )\n }\n if (getWorkbench(cliConfig) || resolveWorkbenchConfig(cliConfig)) {\n return this.output.error(\n '--create is not supported for this app configuration. Omit --create and run sanity deploy.',\n )\n }\n }\n\n const defaultOutputDir = path.resolve(path.join(projectRoot.directory, 'dist'))\n const sourceDir = path.resolve(process.cwd(), this.args.sourceDir || defaultOutputDir)\n\n // Skip the directory check if the studio is externally hosted\n if (this.args.sourceDir && this.args.sourceDir !== 'dist' && !flags.external) {\n let relativeOutput = path.relative(process.cwd(), sourceDir)\n if (relativeOutput[0] !== '.') {\n relativeOutput = `./${relativeOutput}`\n }\n\n if (!this.isUnattended() && !flags['dry-run']) {\n const isEmpty = await dirIsEmptyOrNonExistent(sourceDir)\n const shouldProceed =\n isEmpty ||\n (await confirm({\n default: false,\n message: `\"${relativeOutput}\" is not empty, do you want to proceed?`,\n }))\n\n if (!shouldProceed) {\n this.output.error('Cancelled.', {exit: exitCodes.RUNTIME_ERROR})\n }\n }\n\n // Keep --json's stdout clean for the payload\n if (!flags.json) this.output.log(`Building to ${relativeOutput}\\n`)\n }\n\n // Force yes downstream: build/app resolution otherwise prompts for prerelease/version choices\n const deployFlags = this.isUnattended() || flags['dry-run'] ? {...flags, yes: true} : flags\n\n if (isApp) {\n deployDebug('Deploying app')\n await deployApp({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n } else {\n deployDebug('Deploying studio')\n await deployStudio({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n }\n }\n}\n"],"names":["path","Args","Flags","exitCodes","SanityCommand","confirm","getWorkbench","resolveWorkbenchConfig","deployApp","deployDebug","deployStudio","getAppId","determineIsApp","dirIsEmptyOrNonExistent","DeployCommand","args","sourceDir","directory","description","examples","command","flags","boolean","allowNo","deprecated","build","default","create","exclusive","external","json","char","minify","title","string","url","verbose","yes","run","parse","cliConfig","getCliConfig","projectRoot","getProjectRoot","isApp","output","error","defaultOutputDir","resolve","join","process","cwd","relativeOutput","relative","isUnattended","isEmpty","shouldProceed","message","exit","RUNTIME_ERROR","log","deployFlags"],"mappings":"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"}
|
|
@@ -6,14 +6,41 @@ import { boxen, spinner } from '@sanity/cli-core/ux';
|
|
|
6
6
|
import { pipe, scan, tap } from 'rxjs';
|
|
7
7
|
import { importer } from '../../actions/media/importMedia.js';
|
|
8
8
|
import { importMediaDebug } from '../../actions/media/importMediaDebug.js';
|
|
9
|
+
import { ingestMediaAssetFromUrlWithProgress } from '../../actions/media/ingestMediaAssetFromUrlWithProgress.js';
|
|
9
10
|
import { promptForMediaLibrary } from '../../prompts/promptForMediaLibrary.js';
|
|
10
11
|
import { promptForProject } from '../../prompts/promptForProject.js';
|
|
11
12
|
import { getMediaLibraries } from '../../services/mediaLibraries.js';
|
|
13
|
+
import { getAssetFilenameError, getIngestUrlError } from '../../util/assetSourceValidation.js';
|
|
14
|
+
import { getAssetUploadErrorMessage } from '../../util/assetUploadErrors.js';
|
|
15
|
+
import { isRemoteAssetSource } from '../../util/isRemoteAssetSource.js';
|
|
16
|
+
import { parseAspectFlags } from '../../util/parseAspectFlags.js';
|
|
12
17
|
import { getProjectIdFlag } from '../../util/sharedFlags.js';
|
|
18
|
+
import { defineCommandTelemetry } from '../../util/telemetry/commandTelemetry.js';
|
|
19
|
+
const flags = {
|
|
20
|
+
...getProjectIdFlag({
|
|
21
|
+
description: 'Project ID to import media to',
|
|
22
|
+
semantics: 'override'
|
|
23
|
+
}),
|
|
24
|
+
aspect: Flags.string({
|
|
25
|
+
description: 'Aspect value to set on the imported asset, as key=value. Repeatable. Only applies when the source is a URL - directory and archive imports read aspects from their data.ndjson',
|
|
26
|
+
helpValue: '<key=value>',
|
|
27
|
+
multiple: true
|
|
28
|
+
}),
|
|
29
|
+
filename: Flags.string({
|
|
30
|
+
description: 'Original filename to store on the asset. Only applies when the source is a URL. Defaults to a name derived from the URL',
|
|
31
|
+
helpValue: '<filename>'
|
|
32
|
+
}),
|
|
33
|
+
'media-library-id': requiredWhenUnattended(Flags.string({
|
|
34
|
+
description: 'The id of the target media library'
|
|
35
|
+
})),
|
|
36
|
+
'replace-aspects': Flags.boolean({
|
|
37
|
+
description: 'Replace existing aspect data. All versions will be replaced (e.g. published and draft aspect data). Only applies to directory and archive imports'
|
|
38
|
+
})
|
|
39
|
+
};
|
|
13
40
|
export class MediaImportCommand extends SanityCommand {
|
|
14
41
|
static args = {
|
|
15
42
|
source: Args.string({
|
|
16
|
-
description: '
|
|
43
|
+
description: 'Directory, archive, or asset URL to import from. An https URL imports one asset that Sanity fetches itself',
|
|
17
44
|
required: true
|
|
18
45
|
})
|
|
19
46
|
};
|
|
@@ -30,24 +57,54 @@ export class MediaImportCommand extends SanityCommand {
|
|
|
30
57
|
{
|
|
31
58
|
command: '<%= config.bin %> <%= command.id %> products --replace-aspects',
|
|
32
59
|
description: 'Import all assets from the "products" directory and replace aspects'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
command: '<%= config.bin %> <%= command.id %> https://example.com/hero.png',
|
|
63
|
+
description: 'Have Sanity fetch a single asset from a public URL'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
command: '<%= config.bin %> <%= command.id %> https://example.com/hero.png --aspect department=Brand',
|
|
67
|
+
description: 'Fetch an asset from a URL and set aspect data on it'
|
|
33
68
|
}
|
|
34
69
|
];
|
|
35
|
-
static flags =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})),
|
|
43
|
-
'replace-aspects': Flags.boolean({
|
|
44
|
-
description: 'Replace existing aspect data. All versions will be replaced (e.g. published and draft aspect data)'
|
|
45
|
-
})
|
|
46
|
-
};
|
|
70
|
+
static flags = flags;
|
|
71
|
+
static telemetry = defineCommandTelemetry(flags, {
|
|
72
|
+
redact: [
|
|
73
|
+
'aspect',
|
|
74
|
+
'filename'
|
|
75
|
+
]
|
|
76
|
+
});
|
|
47
77
|
async run() {
|
|
48
78
|
const { args, flags } = await this.parse(MediaImportCommand);
|
|
49
79
|
const { source } = args;
|
|
50
80
|
const replaceAspects = flags['replace-aspects'];
|
|
81
|
+
const isUrlSource = isRemoteAssetSource(source);
|
|
82
|
+
const aspects = this.resolveAspects({
|
|
83
|
+
aspectFlags: flags.aspect,
|
|
84
|
+
isUrlSource
|
|
85
|
+
});
|
|
86
|
+
if (!isUrlSource && flags.filename !== undefined) {
|
|
87
|
+
this.error('The --filename flag only applies when importing from a URL. Directory and archive imports keep each asset’s own filename.', {
|
|
88
|
+
exit: exitCodes.USAGE_ERROR
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (isUrlSource && replaceAspects) {
|
|
92
|
+
this.error('The --replace-aspects flag only applies to directory and archive imports. Aspects passed with --aspect are set when the asset is created.', {
|
|
93
|
+
exit: exitCodes.USAGE_ERROR
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const filenameError = flags.filename === undefined ? undefined : getAssetFilenameError(flags.filename);
|
|
97
|
+
if (filenameError) {
|
|
98
|
+
this.error(filenameError, {
|
|
99
|
+
exit: exitCodes.USAGE_ERROR
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const sourceUrlError = isUrlSource ? getIngestUrlError(source) : undefined;
|
|
103
|
+
if (sourceUrlError) {
|
|
104
|
+
this.error(sourceUrlError, {
|
|
105
|
+
exit: exitCodes.USAGE_ERROR
|
|
106
|
+
});
|
|
107
|
+
}
|
|
51
108
|
const projectId = await this.getProjectId({
|
|
52
109
|
fallback: ()=>promptForProject({})
|
|
53
110
|
});
|
|
@@ -85,6 +142,19 @@ export class MediaImportCommand extends SanityCommand {
|
|
|
85
142
|
exit: exitCodes.RUNTIME_ERROR
|
|
86
143
|
});
|
|
87
144
|
}
|
|
145
|
+
if (isUrlSource) {
|
|
146
|
+
await this.importFromUrl({
|
|
147
|
+
...aspects ? {
|
|
148
|
+
aspects
|
|
149
|
+
} : {},
|
|
150
|
+
...flags.filename ? {
|
|
151
|
+
filename: flags.filename
|
|
152
|
+
} : {},
|
|
153
|
+
mediaLibraryId,
|
|
154
|
+
url: source
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
88
158
|
const projectClient = await getProjectCliClient({
|
|
89
159
|
apiVersion: 'v2025-02-19',
|
|
90
160
|
dataset,
|
|
@@ -142,6 +212,53 @@ export class MediaImportCommand extends SanityCommand {
|
|
|
142
212
|
});
|
|
143
213
|
});
|
|
144
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Import one asset that Sanity fetches from a URL itself.
|
|
217
|
+
*
|
|
218
|
+
* Nothing but the resulting documents goes to stdout, so the output stays
|
|
219
|
+
* consumable as JSON; the wait is reported on stderr by the spinner.
|
|
220
|
+
*/ async importFromUrl(options) {
|
|
221
|
+
const { aspects, filename, mediaLibraryId, url } = options;
|
|
222
|
+
let result;
|
|
223
|
+
try {
|
|
224
|
+
result = await ingestMediaAssetFromUrlWithProgress({
|
|
225
|
+
...aspects ? {
|
|
226
|
+
aspects
|
|
227
|
+
} : {},
|
|
228
|
+
...filename ? {
|
|
229
|
+
filename
|
|
230
|
+
} : {},
|
|
231
|
+
isInteractive: this.resolveIsInteractive(),
|
|
232
|
+
logToStderr: (message)=>this.logToStderr(message),
|
|
233
|
+
mediaLibraryId,
|
|
234
|
+
url
|
|
235
|
+
});
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (error instanceof Error && error.message === 'SIGINT') throw error;
|
|
238
|
+
importMediaDebug('Error importing asset from URL', error);
|
|
239
|
+
this.error(getAssetUploadErrorMessage(error, {
|
|
240
|
+
fromUrl: true,
|
|
241
|
+
target: 'media-library'
|
|
242
|
+
}), {
|
|
243
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
this.log(JSON.stringify({
|
|
247
|
+
asset: {
|
|
248
|
+
_id: result.asset._id,
|
|
249
|
+
_type: result.asset._type,
|
|
250
|
+
assetType: result.asset.assetType
|
|
251
|
+
},
|
|
252
|
+
assetInstance: {
|
|
253
|
+
_id: result.assetInstance._id,
|
|
254
|
+
extension: result.assetInstance.extension,
|
|
255
|
+
mimeType: result.assetInstance.mimeType,
|
|
256
|
+
originalFilename: result.assetInstance.originalFilename,
|
|
257
|
+
size: result.assetInstance.size,
|
|
258
|
+
url: result.assetInstance.url
|
|
259
|
+
}
|
|
260
|
+
}, null, 2));
|
|
261
|
+
}
|
|
145
262
|
reportResult(spin) {
|
|
146
263
|
let previousState;
|
|
147
264
|
return pipe(scan((processedAssetsCount, state)=>[
|
|
@@ -158,6 +275,25 @@ export class MediaImportCommand extends SanityCommand {
|
|
|
158
275
|
}
|
|
159
276
|
}));
|
|
160
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Validate `--aspect` against the source kind and shape it for the request.
|
|
280
|
+
* Runs before any network call so a malformed flag fails immediately.
|
|
281
|
+
*/ resolveAspects(options) {
|
|
282
|
+
const { aspectFlags, isUrlSource } = options;
|
|
283
|
+
if (!aspectFlags?.length) return undefined;
|
|
284
|
+
if (!isUrlSource) {
|
|
285
|
+
this.error('The --aspect flag only applies when importing from a URL. Directory and archive imports read aspect data from their data.ndjson file.', {
|
|
286
|
+
exit: exitCodes.USAGE_ERROR
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const parsed = parseAspectFlags(aspectFlags);
|
|
290
|
+
if ('error' in parsed) {
|
|
291
|
+
this.error(parsed.error, {
|
|
292
|
+
exit: exitCodes.USAGE_ERROR
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return parsed.aspects;
|
|
296
|
+
}
|
|
161
297
|
}
|
|
162
298
|
|
|
163
299
|
//# sourceMappingURL=import.js.map
|