@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
package/README.md
CHANGED
|
@@ -244,17 +244,20 @@ EXAMPLES
|
|
|
244
244
|
|
|
245
245
|
## `sanity assets upload`
|
|
246
246
|
|
|
247
|
-
Upload one
|
|
247
|
+
Upload one image or file to a Sanity dataset, from a local path or a URL, and print the asset document as JSON
|
|
248
248
|
|
|
249
249
|
```
|
|
250
250
|
USAGE
|
|
251
|
-
$ sanity assets upload
|
|
252
|
-
[--type image|file]
|
|
251
|
+
$ sanity assets upload [-p <id>] [-d <name>] [--content-type <mime-type>] [--file <path>] [--filename <filename>]
|
|
252
|
+
[--from-url <url>] [--type image|file]
|
|
253
253
|
|
|
254
254
|
FLAGS
|
|
255
|
-
--content-type=<mime-type> MIME type of the asset, such as image/png or application/pdf
|
|
256
|
-
--file=<path>
|
|
257
|
-
--filename=<filename> Original filename stored on the asset document. Defaults to the local filename
|
|
255
|
+
--content-type=<mime-type> MIME type of the asset, such as image/png or application/pdf. Only applies to --file
|
|
256
|
+
--file=<path> Path to the local file to upload
|
|
257
|
+
--filename=<filename> Original filename stored on the asset document. Defaults to the local filename when using
|
|
258
|
+
--file
|
|
259
|
+
--from-url=<url> https URL for Sanity to fetch the asset from, instead of uploading a local file. Must be
|
|
260
|
+
reachable from the public internet without authentication
|
|
258
261
|
--type=<option> [default: image] Asset type to create
|
|
259
262
|
<options: image|file>
|
|
260
263
|
|
|
@@ -263,7 +266,7 @@ OVERRIDE FLAGS
|
|
|
263
266
|
-p, --project-id=<id> Project ID to upload the asset to (overrides CLI configuration)
|
|
264
267
|
|
|
265
268
|
DESCRIPTION
|
|
266
|
-
Upload one
|
|
269
|
+
Upload one image or file to a Sanity dataset, from a local path or a URL, and print the asset document as JSON
|
|
267
270
|
|
|
268
271
|
EXAMPLES
|
|
269
272
|
Upload an image using the configured project
|
|
@@ -274,6 +277,10 @@ EXAMPLES
|
|
|
274
277
|
|
|
275
278
|
$ sanity assets upload --file ./brief.pdf --type file --content-type application/pdf --project-id abc123 \
|
|
276
279
|
--dataset production
|
|
280
|
+
|
|
281
|
+
Have Sanity fetch an image from a public URL
|
|
282
|
+
|
|
283
|
+
$ sanity assets upload --from-url 'https://example.com/hero.png' --type image --dataset production
|
|
277
284
|
```
|
|
278
285
|
|
|
279
286
|
## `sanity backups disable [DATASET]`
|
|
@@ -2001,8 +2008,8 @@ Builds and deploys Sanity Studio or application to Sanity hosting
|
|
|
2001
2008
|
|
|
2002
2009
|
```
|
|
2003
2010
|
USAGE
|
|
2004
|
-
$ sanity deploy [SOURCEDIR] [--auto-updates] [--
|
|
2005
|
-
[-j] [--schema-required] [--
|
|
2011
|
+
$ sanity deploy [SOURCEDIR] [--auto-updates] [--build] [--create | --external | --url <value>] [--dry-run]
|
|
2012
|
+
[-j] [--minify] [--schema-required] [--source-maps] [--title <value>] [--verbose] [-y]
|
|
2006
2013
|
|
|
2007
2014
|
ARGUMENTS
|
|
2008
2015
|
[SOURCEDIR] Source directory
|
|
@@ -2012,13 +2019,14 @@ FLAGS
|
|
|
2012
2019
|
-y, --yes Unattended mode, answers "yes" to any "yes/no" prompt and otherwise uses defaults
|
|
2013
2020
|
--[no-]auto-updates Automatically update the studio to the latest version
|
|
2014
2021
|
--[no-]build Build the studio before deploying (use --no-build to deploy existing `dist/` output)
|
|
2022
|
+
--create Create a new App SDK app, even if the organization has other apps
|
|
2015
2023
|
--dry-run Report what would be deployed without uploading or creating anything
|
|
2016
2024
|
--external Register an externally hosted studio
|
|
2017
2025
|
--[no-]minify Minify built JavaScript (use --no-minify to skip for faster builds)
|
|
2018
2026
|
--schema-required Fail if schema deployment fails
|
|
2019
2027
|
--source-maps Enable source maps for built bundles (increases size of bundle)
|
|
2020
|
-
--title=<value> Title for a newly created application or studio. For apps
|
|
2021
|
-
|
|
2028
|
+
--title=<value> Title for a newly created application or studio. For apps, use --create to select creation
|
|
2029
|
+
when the organization already has apps
|
|
2022
2030
|
--url=<value> Studio URL for deployment. For external studios, the full URL. For hosted studios, the
|
|
2023
2031
|
hostname (e.g. "my-studio" or "my-studio.sanity.studio")
|
|
2024
2032
|
--verbose Enable verbose logging
|
|
@@ -2042,6 +2050,19 @@ EXAMPLES
|
|
|
2042
2050
|
Register an externally hosted studio (studioHost contains full URL)
|
|
2043
2051
|
|
|
2044
2052
|
$ sanity deploy --external
|
|
2053
|
+
|
|
2054
|
+
Create and deploy an App SDK app without prompts. Save application.id as deployment.appId, then omit --create on
|
|
2055
|
+
later deploys
|
|
2056
|
+
|
|
2057
|
+
$ sanity deploy --create --title "My App" --yes --json
|
|
2058
|
+
|
|
2059
|
+
Preview a new App SDK deployment without creating or uploading anything
|
|
2060
|
+
|
|
2061
|
+
$ sanity deploy --create --title "My App" --dry-run --json
|
|
2062
|
+
|
|
2063
|
+
Deploy a studio without prompts using its hostname
|
|
2064
|
+
|
|
2065
|
+
$ sanity deploy --url my-studio --yes
|
|
2045
2066
|
```
|
|
2046
2067
|
|
|
2047
2068
|
## `sanity dev`
|
|
@@ -2511,8 +2532,8 @@ DESCRIPTION
|
|
|
2511
2532
|
|
|
2512
2533
|
Open the emulator in your browser to interactively test your functions with the payload editor.
|
|
2513
2534
|
|
|
2514
|
-
Optionally, set the host and port with the --host and --port flags.
|
|
2515
|
-
|
|
2535
|
+
Optionally, set the host and port with the --host and --port flags. The live-reload WebSocket server shares this host
|
|
2536
|
+
and port. Function timeout can be configured with the --timeout flag.
|
|
2516
2537
|
|
|
2517
2538
|
To invoke a function with the CLI, use 'functions test'.
|
|
2518
2539
|
|
|
@@ -3309,15 +3330,20 @@ Import a set of assets to the target media library.
|
|
|
3309
3330
|
|
|
3310
3331
|
```
|
|
3311
3332
|
USAGE
|
|
3312
|
-
$ sanity media import SOURCE [-p <id>] [--
|
|
3333
|
+
$ sanity media import SOURCE [-p <id>] [--aspect <key=value>...] [--filename <filename>] [--media-library-id
|
|
3334
|
+
<value>] [--replace-aspects]
|
|
3313
3335
|
|
|
3314
3336
|
ARGUMENTS
|
|
3315
|
-
SOURCE
|
|
3337
|
+
SOURCE Directory, archive, or asset URL to import from. An https URL imports one asset that Sanity fetches itself
|
|
3316
3338
|
|
|
3317
3339
|
FLAGS
|
|
3340
|
+
--aspect=<key=value>... Aspect value to set on the imported asset, as key=value. Repeatable. Only applies when the
|
|
3341
|
+
source is a URL - directory and archive imports read aspects from their data.ndjson
|
|
3342
|
+
--filename=<filename> Original filename to store on the asset. Only applies when the source is a URL. Defaults
|
|
3343
|
+
to a name derived from the URL
|
|
3318
3344
|
--media-library-id=<value> The id of the target media library
|
|
3319
3345
|
--replace-aspects Replace existing aspect data. All versions will be replaced (e.g. published and draft
|
|
3320
|
-
aspect data)
|
|
3346
|
+
aspect data). Only applies to directory and archive imports
|
|
3321
3347
|
|
|
3322
3348
|
OVERRIDE FLAGS
|
|
3323
3349
|
-p, --project-id=<id> Project ID to import media to (overrides CLI configuration)
|
|
@@ -3337,6 +3363,14 @@ EXAMPLES
|
|
|
3337
3363
|
Import all assets from the "products" directory and replace aspects
|
|
3338
3364
|
|
|
3339
3365
|
$ sanity media import products --replace-aspects
|
|
3366
|
+
|
|
3367
|
+
Have Sanity fetch a single asset from a public URL
|
|
3368
|
+
|
|
3369
|
+
$ sanity media import https://example.com/hero.png
|
|
3370
|
+
|
|
3371
|
+
Fetch an asset from a URL and set aspect data on it
|
|
3372
|
+
|
|
3373
|
+
$ sanity media import https://example.com/hero.png --aspect department=Brand
|
|
3340
3374
|
```
|
|
3341
3375
|
|
|
3342
3376
|
## `sanity migrations create [TITLE]`
|
|
@@ -4610,18 +4644,25 @@ List workflow instances in the configured dataset (in-flight by default).
|
|
|
4610
4644
|
```
|
|
4611
4645
|
USAGE
|
|
4612
4646
|
$ sanity workflows list [--tag <value>] [--include-completed] [--failed] [--definition <value>] [--document
|
|
4613
|
-
<value>] [--
|
|
4647
|
+
<value>] [--assignment-user <value>] [--assignment-role <value>...] [--assignment-state unrouted|routed|held...]
|
|
4648
|
+
[--limit <value>] [--json]
|
|
4614
4649
|
|
|
4615
4650
|
FLAGS
|
|
4616
|
-
--
|
|
4617
|
-
--
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
--
|
|
4621
|
-
--
|
|
4622
|
-
|
|
4623
|
-
--
|
|
4624
|
-
|
|
4651
|
+
--assignment-role=<value>... A literal project role held by --assignment-user. Repeat for multiple roles.
|
|
4652
|
+
--assignment-state=<option>... Assignment state to include: unrouted, routed (offered through a supplied role), or
|
|
4653
|
+
held. Repeat for multiple states.
|
|
4654
|
+
<options: unrouted|routed|held>
|
|
4655
|
+
--assignment-user=<value> Account-global user id used for viewer-scoped assignment filtering and counts.
|
|
4656
|
+
--definition=<value> Only instances of this workflow definition (its `name`; the instance's `definition`
|
|
4657
|
+
field).
|
|
4658
|
+
--document=<value> Only instances that reference this document (resource-qualified GDR URI, e.g.
|
|
4659
|
+
"dataset:proj:ds:article-1").
|
|
4660
|
+
--failed Only instances with at least one failed activity.
|
|
4661
|
+
--include-completed Include completed/aborted instances (default: in-flight only).
|
|
4662
|
+
--json Emit structured JSON instead of rendered output.
|
|
4663
|
+
--limit=<value> [default: 50] Maximum rows to return.
|
|
4664
|
+
--tag=<value> Workflow environment tag (e.g. prod, test) — an optional query filter that also
|
|
4665
|
+
narrows which resources are searched; omit to span them all.
|
|
4625
4666
|
|
|
4626
4667
|
DESCRIPTION
|
|
4627
4668
|
List workflow instances in the configured dataset (in-flight by default).
|
|
@@ -4635,6 +4676,8 @@ EXAMPLES
|
|
|
4635
4676
|
|
|
4636
4677
|
$ sanity workflows list --document dataset:proj:ds:article-1
|
|
4637
4678
|
|
|
4679
|
+
$ sanity workflows list --assignment-user gAda --assignment-role legal --assignment-state routed
|
|
4680
|
+
|
|
4638
4681
|
$ sanity workflows list --tag prod
|
|
4639
4682
|
|
|
4640
4683
|
$ sanity workflows list --json
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ingestAssetFromUrl } from '../../services/assets.js';
|
|
2
|
+
import { withUrlIngestProgress } from './withUrlIngestProgress.js';
|
|
3
|
+
/**
|
|
4
|
+
* Ingest a dataset asset from a URL, reporting the wait.
|
|
5
|
+
*/ export async function ingestAssetFromUrlWithProgress(options) {
|
|
6
|
+
const { assetType, isInteractive, logToStderr, ...ingestOptions } = options;
|
|
7
|
+
const ingestMessage = `Fetching ${assetType} asset from URL`;
|
|
8
|
+
return withUrlIngestProgress({
|
|
9
|
+
ingest: (signal)=>ingestAssetFromUrl({
|
|
10
|
+
...ingestOptions,
|
|
11
|
+
assetType,
|
|
12
|
+
signal
|
|
13
|
+
}),
|
|
14
|
+
isInteractive,
|
|
15
|
+
logToStderr,
|
|
16
|
+
successMessage: (asset)=>`Uploaded ${assetType} asset: ${asset._id}`,
|
|
17
|
+
waitMessage: `${ingestMessage}. Sanity downloads the asset, which may take several minutes.`
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=ingestAssetFromUrlWithProgress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/assets/ingestAssetFromUrlWithProgress.ts"],"sourcesContent":["import {type AssetType, ingestAssetFromUrl} from '../../services/assets.js'\nimport {withUrlIngestProgress} from './withUrlIngestProgress.js'\n\ninterface IngestAssetFromUrlWithProgressOptions {\n assetType: AssetType\n dataset: string\n isInteractive: boolean\n logToStderr: (message: string) => void\n projectId: string\n url: string\n\n filename?: string\n}\n\n/**\n * Ingest a dataset asset from a URL, reporting the wait.\n */\nexport async function ingestAssetFromUrlWithProgress(\n options: IngestAssetFromUrlWithProgressOptions,\n) {\n const {assetType, isInteractive, logToStderr, ...ingestOptions} = options\n const ingestMessage = `Fetching ${assetType} asset from URL`\n\n return withUrlIngestProgress({\n ingest: (signal) => ingestAssetFromUrl({...ingestOptions, assetType, signal}),\n isInteractive,\n logToStderr,\n successMessage: (asset) => `Uploaded ${assetType} asset: ${asset._id}`,\n waitMessage: `${ingestMessage}. Sanity downloads the asset, which may take several minutes.`,\n })\n}\n"],"names":["ingestAssetFromUrl","withUrlIngestProgress","ingestAssetFromUrlWithProgress","options","assetType","isInteractive","logToStderr","ingestOptions","ingestMessage","ingest","signal","successMessage","asset","_id","waitMessage"],"mappings":"AAAA,SAAwBA,kBAAkB,QAAO,2BAA0B;AAC3E,SAAQC,qBAAqB,QAAO,6BAA4B;AAahE;;CAEC,GACD,OAAO,eAAeC,+BACpBC,OAA8C;IAE9C,MAAM,EAACC,SAAS,EAAEC,aAAa,EAAEC,WAAW,EAAE,GAAGC,eAAc,GAAGJ;IAClE,MAAMK,gBAAgB,CAAC,SAAS,EAAEJ,UAAU,eAAe,CAAC;IAE5D,OAAOH,sBAAsB;QAC3BQ,QAAQ,CAACC,SAAWV,mBAAmB;gBAAC,GAAGO,aAAa;gBAAEH;gBAAWM;YAAM;QAC3EL;QACAC;QACAK,gBAAgB,CAACC,QAAU,CAAC,SAAS,EAAER,UAAU,QAAQ,EAAEQ,MAAMC,GAAG,EAAE;QACtEC,aAAa,GAAGN,cAAc,6DAA6D,CAAC;IAC9F;AACF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getCliExecutionContext } from '@sanity/cli-core/executionContext';
|
|
2
|
+
import { spinner } from '@sanity/cli-core/ux';
|
|
3
|
+
/**
|
|
4
|
+
* Report progress for an asset ingest where Sanity fetches the source itself.
|
|
5
|
+
*
|
|
6
|
+
* The bytes never pass through this process, so there is no upload percentage
|
|
7
|
+
* to report — only a single wait that can run into minutes. The spinner exists
|
|
8
|
+
* to keep that wait legible rather than to track it.
|
|
9
|
+
*
|
|
10
|
+
* Shared by the dataset and media library ingest paths because the interrupt
|
|
11
|
+
* handling below is easy to get subtly wrong, not because the two have much
|
|
12
|
+
* else in common.
|
|
13
|
+
*/ export async function withUrlIngestProgress({ ingest, isInteractive, logToStderr, successMessage, waitMessage }) {
|
|
14
|
+
const executionContext = getCliExecutionContext();
|
|
15
|
+
const isTTY = !executionContext && isInteractive;
|
|
16
|
+
const ingestProgress = spinner(waitMessage).start();
|
|
17
|
+
if (executionContext) logToStderr(waitMessage);
|
|
18
|
+
const ingestController = new AbortController();
|
|
19
|
+
const interruptIngest = ()=>ingestController.abort(new Error('SIGINT'));
|
|
20
|
+
const handlesInterrupt = !executionContext;
|
|
21
|
+
if (handlesInterrupt) process.once('SIGINT', interruptIngest);
|
|
22
|
+
// Ora uses raw mode for Ctrl+C, but its input listener does not always make stdin flow.
|
|
23
|
+
const resumesInterruptInput = handlesInterrupt && isTTY && ingestProgress.isSpinning && process.stdin.isTTY && process.stdin.isRaw && process.stdin.readableFlowing !== true;
|
|
24
|
+
if (resumesInterruptInput) process.stdin.resume();
|
|
25
|
+
try {
|
|
26
|
+
const result = await ingest(ingestController.signal);
|
|
27
|
+
const message = successMessage(result);
|
|
28
|
+
if (executionContext) {
|
|
29
|
+
ingestProgress.stop();
|
|
30
|
+
logToStderr(message);
|
|
31
|
+
} else {
|
|
32
|
+
ingestProgress.succeed(message);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
ingestProgress.stop();
|
|
37
|
+
ingestController.signal.throwIfAborted();
|
|
38
|
+
throw error;
|
|
39
|
+
} finally{
|
|
40
|
+
if (handlesInterrupt) process.off('SIGINT', interruptIngest);
|
|
41
|
+
if (resumesInterruptInput) process.stdin.pause();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=withUrlIngestProgress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/assets/withUrlIngestProgress.ts"],"sourcesContent":["import {getCliExecutionContext} from '@sanity/cli-core/executionContext'\nimport {spinner} from '@sanity/cli-core/ux'\n\ninterface WithUrlIngestProgressOptions<T> {\n /**\n * Performs the ingest. Receives a signal that aborts with an `Error('SIGINT')`\n * when the user interrupts, so callers can pass it straight to the request.\n */\n ingest: (signal: AbortSignal) => Promise<T>\n isInteractive: boolean\n logToStderr: (message: string) => void\n successMessage: (result: T) => string\n waitMessage: string\n}\n\n/**\n * Report progress for an asset ingest where Sanity fetches the source itself.\n *\n * The bytes never pass through this process, so there is no upload percentage\n * to report — only a single wait that can run into minutes. The spinner exists\n * to keep that wait legible rather than to track it.\n *\n * Shared by the dataset and media library ingest paths because the interrupt\n * handling below is easy to get subtly wrong, not because the two have much\n * else in common.\n */\nexport async function withUrlIngestProgress<T>({\n ingest,\n isInteractive,\n logToStderr,\n successMessage,\n waitMessage,\n}: WithUrlIngestProgressOptions<T>): Promise<T> {\n const executionContext = getCliExecutionContext()\n const isTTY = !executionContext && isInteractive\n const ingestProgress = spinner(waitMessage).start()\n if (executionContext) logToStderr(waitMessage)\n\n const ingestController = new AbortController()\n const interruptIngest = () => ingestController.abort(new Error('SIGINT'))\n const handlesInterrupt = !executionContext\n if (handlesInterrupt) process.once('SIGINT', interruptIngest)\n\n // Ora uses raw mode for Ctrl+C, but its input listener does not always make stdin flow.\n const resumesInterruptInput =\n handlesInterrupt &&\n isTTY &&\n ingestProgress.isSpinning &&\n process.stdin.isTTY &&\n process.stdin.isRaw &&\n process.stdin.readableFlowing !== true\n if (resumesInterruptInput) process.stdin.resume()\n\n try {\n const result = await ingest(ingestController.signal)\n const message = successMessage(result)\n\n if (executionContext) {\n ingestProgress.stop()\n logToStderr(message)\n } else {\n ingestProgress.succeed(message)\n }\n\n return result\n } catch (error) {\n ingestProgress.stop()\n ingestController.signal.throwIfAborted()\n throw error\n } finally {\n if (handlesInterrupt) process.off('SIGINT', interruptIngest)\n if (resumesInterruptInput) process.stdin.pause()\n }\n}\n"],"names":["getCliExecutionContext","spinner","withUrlIngestProgress","ingest","isInteractive","logToStderr","successMessage","waitMessage","executionContext","isTTY","ingestProgress","start","ingestController","AbortController","interruptIngest","abort","Error","handlesInterrupt","process","once","resumesInterruptInput","isSpinning","stdin","isRaw","readableFlowing","resume","result","signal","message","stop","succeed","error","throwIfAborted","off","pause"],"mappings":"AAAA,SAAQA,sBAAsB,QAAO,oCAAmC;AACxE,SAAQC,OAAO,QAAO,sBAAqB;AAc3C;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,sBAAyB,EAC7CC,MAAM,EACNC,aAAa,EACbC,WAAW,EACXC,cAAc,EACdC,WAAW,EACqB;IAChC,MAAMC,mBAAmBR;IACzB,MAAMS,QAAQ,CAACD,oBAAoBJ;IACnC,MAAMM,iBAAiBT,QAAQM,aAAaI,KAAK;IACjD,IAAIH,kBAAkBH,YAAYE;IAElC,MAAMK,mBAAmB,IAAIC;IAC7B,MAAMC,kBAAkB,IAAMF,iBAAiBG,KAAK,CAAC,IAAIC,MAAM;IAC/D,MAAMC,mBAAmB,CAACT;IAC1B,IAAIS,kBAAkBC,QAAQC,IAAI,CAAC,UAAUL;IAE7C,wFAAwF;IACxF,MAAMM,wBACJH,oBACAR,SACAC,eAAeW,UAAU,IACzBH,QAAQI,KAAK,CAACb,KAAK,IACnBS,QAAQI,KAAK,CAACC,KAAK,IACnBL,QAAQI,KAAK,CAACE,eAAe,KAAK;IACpC,IAAIJ,uBAAuBF,QAAQI,KAAK,CAACG,MAAM;IAE/C,IAAI;QACF,MAAMC,SAAS,MAAMvB,OAAOS,iBAAiBe,MAAM;QACnD,MAAMC,UAAUtB,eAAeoB;QAE/B,IAAIlB,kBAAkB;YACpBE,eAAemB,IAAI;YACnBxB,YAAYuB;QACd,OAAO;YACLlB,eAAeoB,OAAO,CAACF;QACzB;QAEA,OAAOF;IACT,EAAE,OAAOK,OAAO;QACdrB,eAAemB,IAAI;QACnBjB,iBAAiBe,MAAM,CAACK,cAAc;QACtC,MAAMD;IACR,SAAU;QACR,IAAId,kBAAkBC,QAAQe,GAAG,CAAC,UAAUnB;QAC5C,IAAIM,uBAAuBF,QAAQI,KAAK,CAACY,KAAK;IAChD;AACF"}
|
|
@@ -3,22 +3,19 @@ import os from 'node:os';
|
|
|
3
3
|
import { getSanityUrl, subdebug } from '@sanity/cli-core';
|
|
4
4
|
import { getTokenDetails } from '../../services/auth.js';
|
|
5
5
|
const debug = subdebug('auth');
|
|
6
|
-
const defaultCallbackPort =
|
|
6
|
+
const defaultCallbackPort = 0;
|
|
7
7
|
const callbackEndpoint = '/callback';
|
|
8
8
|
/**
|
|
9
9
|
* Get the port to first attempt binding the auth callback server to.
|
|
10
10
|
*
|
|
11
11
|
* The auth backend accepts any `http://localhost:<port>` origin for token
|
|
12
|
-
* callbacks,
|
|
13
|
-
* need to allow or forward the port (e.g. logging in from a remote machine over
|
|
14
|
-
* an SSH tunnel). If the preferred port is taken, we fall back to an
|
|
15
|
-
* OS-assigned ephemeral port.
|
|
12
|
+
* callbacks, so by default we let the OS assign an available ephemeral port.
|
|
16
13
|
*
|
|
17
14
|
* The `SANITY_CLI_CALLBACK_PORT` environment variable overrides the preferred
|
|
18
15
|
* port (`0` for an OS-assigned port). The override exists primarily for tests,
|
|
19
16
|
* where OS-assigned ports prevent collisions between tests running in parallel.
|
|
20
17
|
*
|
|
21
|
-
* @returns Port number to attempt first
|
|
18
|
+
* @returns Port number to attempt first (`0` for an OS-assigned port)
|
|
22
19
|
* @internal
|
|
23
20
|
*/ function getPreferredCallbackPort() {
|
|
24
21
|
const override = process.env.SANITY_CLI_CALLBACK_PORT;
|
|
@@ -49,7 +46,7 @@ const platformNames = {
|
|
|
49
46
|
* do a request to the `/auth/fetch` endpoint with to get the actual auth token,
|
|
50
47
|
* invalidating the SID in the process.
|
|
51
48
|
*
|
|
52
|
-
* If
|
|
49
|
+
* If an explicitly configured port is busy, we retry with an OS-assigned port.
|
|
53
50
|
*
|
|
54
51
|
* @param providerUrl - The URL of the login provider
|
|
55
52
|
* @returns Resolves with HTTP server instance, a login URL to send user to, and a `token` promise
|
|
@@ -125,7 +122,7 @@ const platformNames = {
|
|
|
125
122
|
});
|
|
126
123
|
server.on('error', function onCallbackServerError(err) {
|
|
127
124
|
// The auth backend accepts any localhost port in the callback origin, so if
|
|
128
|
-
//
|
|
125
|
+
// an explicitly configured port is busy we fall back to an OS-assigned ephemeral port.
|
|
129
126
|
// Port 0 cannot itself be "in use" - an EADDRINUSE there means something is
|
|
130
127
|
// genuinely wrong, so only fall back once.
|
|
131
128
|
if ('code' in err && err.code === 'EADDRINUSE' && callbackPort !== 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/auth/authServer.ts"],"sourcesContent":["import {createServer, type Server} from 'node:http'\nimport os from 'node:os'\n\nimport {getSanityUrl, subdebug} from '@sanity/cli-core'\n\nimport {getTokenDetails} from '../../services/auth.js'\nimport {type TokenDetails} from './types.js'\n\nconst debug = subdebug('auth')\nconst defaultCallbackPort = 4321\nconst callbackEndpoint = '/callback'\n\n/**\n * Get the port to first attempt binding the auth callback server to.\n *\n * The auth backend accepts any `http://localhost:<port>` origin for token\n * callbacks, but we prefer a predictable port: it is friendlier for users who\n * need to allow or forward the port (e.g. logging in from a remote machine over\n * an SSH tunnel). If the preferred port is taken, we fall back to an\n * OS-assigned ephemeral port.\n *\n * The `SANITY_CLI_CALLBACK_PORT` environment variable overrides the preferred\n * port (`0` for an OS-assigned port). The override exists primarily for tests,\n * where OS-assigned ports prevent collisions between tests running in parallel.\n *\n * @returns Port number to attempt first\n * @internal\n */\nfunction getPreferredCallbackPort(): number {\n const override = process.env.SANITY_CLI_CALLBACK_PORT\n if (!override) {\n return defaultCallbackPort\n }\n\n debug('Using callback port from SANITY_CLI_CALLBACK_PORT: %s', override)\n\n const port = Number.parseInt(override.trim(), 10)\n if (Number.isNaN(port) || port < 0 || port > 65_535) {\n throw new Error(`Invalid SANITY_CLI_CALLBACK_PORT value: \"${override}\"`)\n }\n\n return port\n}\n\nconst platformNames: Record<string, string | undefined> = {\n aix: 'AIX',\n android: 'Android',\n darwin: 'MacOS',\n freebsd: 'FreeBSD',\n linux: 'Linux',\n openbsd: 'OpenBSD',\n sunos: 'SunOS',\n win32: 'Windows',\n}\n\n/**\n * Start a local HTTP server and wait for a request to the auth callback endpoint.\n * This happens by the user being sent to a login page with a callback URL that points to\n * this local server. This request includes a short-lived \"SID\" (session ID) that we then\n * do a request to the `/auth/fetch` endpoint with to get the actual auth token,\n * invalidating the SID in the process.\n *\n * If we fail to bind to the preferred port, we retry with an OS-assigned port.\n *\n * @param providerUrl - The URL of the login provider\n * @returns Resolves with HTTP server instance, a login URL to send user to, and a `token` promise\n * @internal\n */\nexport function startServerForTokenCallback(\n providerUrl: string,\n): Promise<{loginUrl: URL; server: Server; token: Promise<TokenDetails>}> {\n const sanityUrl = getSanityUrl()\n\n // note: replace with `Promise.withResolvers()` when minimum Node.js is 22+\n let resolveToken: (resolvedToken: PromiseLike<TokenDetails> | TokenDetails) => void\n let rejectToken: (reason: Error) => void\n const tokenPromise = new Promise<TokenDetails>((resolve, reject) => {\n resolveToken = resolve\n rejectToken = reject\n })\n\n return new Promise((resolve, reject) => {\n let callbackPort = getPreferredCallbackPort()\n\n const server = createServer(async function onCallbackServerRequest(req, res) {\n function failLoginRequest(code = '') {\n res.writeHead(303, 'See Other', {\n Connection: 'close',\n Location: `${sanityUrl}/login/error${code ? `?error=${code}` : ''}`,\n })\n res.end()\n server.close()\n }\n\n const url = new URL(req.url || '/', `http://localhost:${callbackPort}`)\n if (url.pathname !== callbackEndpoint) {\n res.writeHead(404, 'Not Found', {Connection: 'close', 'Content-Type': 'text/plain'})\n res.write('404 Not Found')\n res.end()\n return\n }\n\n const absoluteTokenUrl = url.searchParams.get('url')\n if (!absoluteTokenUrl) {\n failLoginRequest()\n rejectToken(new Error('Missing callback URL'))\n return\n }\n\n const tokenUrl = new URL(absoluteTokenUrl)\n if (!tokenUrl.searchParams.has('sid')) {\n failLoginRequest('NO_SESSION_ID')\n rejectToken(new Error('Missing session ID in callback'))\n return\n }\n\n let token: TokenDetails\n try {\n token = await getTokenDetails(tokenUrl.search)\n } catch (err) {\n failLoginRequest('UNRESOLVED_SESSION')\n rejectToken(err instanceof Error ? err : new Error(`Unknown error: ${err}`))\n return\n }\n\n res.writeHead(303, 'See Other', {\n Connection: 'close',\n Location: `${sanityUrl}/login/success`,\n })\n res.end()\n server.close()\n resolveToken(token)\n })\n\n server.on('listening', function onCallbackListen() {\n // Once the server is successfully listening on a port, we can return the promise.\n // We'll then await the _token promise_, while the server is running in the background.\n const callbackUrl = getCallbackUrl(server)\n const loginUrl = getLoginUrl(providerUrl, callbackUrl)\n resolve({loginUrl, server, token: tokenPromise})\n })\n\n server.on('error', function onCallbackServerError(err) {\n // The auth backend accepts any localhost port in the callback origin, so if\n // the preferred port is busy we fall back to an OS-assigned ephemeral port.\n // Port 0 cannot itself be \"in use\" - an EADDRINUSE there means something is\n // genuinely wrong, so only fall back once.\n if ('code' in err && err.code === 'EADDRINUSE' && callbackPort !== 0) {\n debug('Port %d busy, falling back to OS-assigned port', callbackPort)\n callbackPort = 0\n server.listen(callbackPort)\n } else {\n reject(err)\n }\n })\n\n debug('Starting callback server on port %d', callbackPort)\n server.listen(callbackPort)\n })\n}\n\n/**\n * Get the login URL to send the user to for the given auth provider.\n *\n * The generated URL will include a label for the session that includes the\n * hostname and platform of the current computer, to help identify the session.\n *\n * @param providerUrl - The URL of the login provider\n * @param callbackUrl - The callback URL for the local auth token server\n * @returns The login URL\n * @internal\n */\nfunction getLoginUrl(providerUrl: string, callbackUrl: URL): URL {\n // Build a login URL that redirects back back to OAuth flow on success\n const loginUrl = new URL(providerUrl)\n\n // Prefer `MacOS` over `darwin` etc\n const platformName = os.platform()\n const platform = platformName in platformNames ? platformNames[platformName] : platformName\n\n // Prefer `espens-macbook` over `espens-macbook.local`\n const hostname = os.hostname().replaceAll(/\\.(local|lan)$/g, '')\n\n loginUrl.searchParams.set('type', 'token')\n loginUrl.searchParams.set('label', `${hostname} / ${platform}`)\n loginUrl.searchParams.set('origin', callbackUrl.href)\n\n return loginUrl\n}\n\nfunction getCallbackUrl(server: Server): URL {\n const serverUrl = server.address()\n if (!serverUrl || typeof serverUrl === 'string') {\n // Note: `serverUrl` is string only when binding to unix sockets,\n // thus we can safely assume Something Is Wrong™ if it's a string\n throw new Error('Failed to start auth callback server')\n }\n\n return new URL(callbackEndpoint, `http://localhost:${serverUrl.port}`)\n}\n"],"names":["createServer","os","getSanityUrl","subdebug","getTokenDetails","debug","defaultCallbackPort","callbackEndpoint","getPreferredCallbackPort","override","process","env","SANITY_CLI_CALLBACK_PORT","port","Number","parseInt","trim","isNaN","Error","platformNames","aix","android","darwin","freebsd","linux","openbsd","sunos","win32","startServerForTokenCallback","providerUrl","sanityUrl","resolveToken","rejectToken","tokenPromise","Promise","resolve","reject","callbackPort","server","onCallbackServerRequest","req","res","failLoginRequest","code","writeHead","Connection","Location","end","close","url","URL","pathname","write","absoluteTokenUrl","searchParams","get","tokenUrl","has","token","search","err","on","onCallbackListen","callbackUrl","getCallbackUrl","loginUrl","getLoginUrl","onCallbackServerError","listen","platformName","platform","hostname","replaceAll","set","href","serverUrl","address"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/auth/authServer.ts"],"sourcesContent":["import {createServer, type Server} from 'node:http'\nimport os from 'node:os'\n\nimport {getSanityUrl, subdebug} from '@sanity/cli-core'\n\nimport {getTokenDetails} from '../../services/auth.js'\nimport {type TokenDetails} from './types.js'\n\nconst debug = subdebug('auth')\nconst defaultCallbackPort = 0\nconst callbackEndpoint = '/callback'\n\n/**\n * Get the port to first attempt binding the auth callback server to.\n *\n * The auth backend accepts any `http://localhost:<port>` origin for token\n * callbacks, so by default we let the OS assign an available ephemeral port.\n *\n * The `SANITY_CLI_CALLBACK_PORT` environment variable overrides the preferred\n * port (`0` for an OS-assigned port). The override exists primarily for tests,\n * where OS-assigned ports prevent collisions between tests running in parallel.\n *\n * @returns Port number to attempt first (`0` for an OS-assigned port)\n * @internal\n */\nfunction getPreferredCallbackPort(): number {\n const override = process.env.SANITY_CLI_CALLBACK_PORT\n if (!override) {\n return defaultCallbackPort\n }\n\n debug('Using callback port from SANITY_CLI_CALLBACK_PORT: %s', override)\n\n const port = Number.parseInt(override.trim(), 10)\n if (Number.isNaN(port) || port < 0 || port > 65_535) {\n throw new Error(`Invalid SANITY_CLI_CALLBACK_PORT value: \"${override}\"`)\n }\n\n return port\n}\n\nconst platformNames: Record<string, string | undefined> = {\n aix: 'AIX',\n android: 'Android',\n darwin: 'MacOS',\n freebsd: 'FreeBSD',\n linux: 'Linux',\n openbsd: 'OpenBSD',\n sunos: 'SunOS',\n win32: 'Windows',\n}\n\n/**\n * Start a local HTTP server and wait for a request to the auth callback endpoint.\n * This happens by the user being sent to a login page with a callback URL that points to\n * this local server. This request includes a short-lived \"SID\" (session ID) that we then\n * do a request to the `/auth/fetch` endpoint with to get the actual auth token,\n * invalidating the SID in the process.\n *\n * If an explicitly configured port is busy, we retry with an OS-assigned port.\n *\n * @param providerUrl - The URL of the login provider\n * @returns Resolves with HTTP server instance, a login URL to send user to, and a `token` promise\n * @internal\n */\nexport function startServerForTokenCallback(\n providerUrl: string,\n): Promise<{loginUrl: URL; server: Server; token: Promise<TokenDetails>}> {\n const sanityUrl = getSanityUrl()\n\n // note: replace with `Promise.withResolvers()` when minimum Node.js is 22+\n let resolveToken: (resolvedToken: PromiseLike<TokenDetails> | TokenDetails) => void\n let rejectToken: (reason: Error) => void\n const tokenPromise = new Promise<TokenDetails>((resolve, reject) => {\n resolveToken = resolve\n rejectToken = reject\n })\n\n return new Promise((resolve, reject) => {\n let callbackPort = getPreferredCallbackPort()\n\n const server = createServer(async function onCallbackServerRequest(req, res) {\n function failLoginRequest(code = '') {\n res.writeHead(303, 'See Other', {\n Connection: 'close',\n Location: `${sanityUrl}/login/error${code ? `?error=${code}` : ''}`,\n })\n res.end()\n server.close()\n }\n\n const url = new URL(req.url || '/', `http://localhost:${callbackPort}`)\n if (url.pathname !== callbackEndpoint) {\n res.writeHead(404, 'Not Found', {Connection: 'close', 'Content-Type': 'text/plain'})\n res.write('404 Not Found')\n res.end()\n return\n }\n\n const absoluteTokenUrl = url.searchParams.get('url')\n if (!absoluteTokenUrl) {\n failLoginRequest()\n rejectToken(new Error('Missing callback URL'))\n return\n }\n\n const tokenUrl = new URL(absoluteTokenUrl)\n if (!tokenUrl.searchParams.has('sid')) {\n failLoginRequest('NO_SESSION_ID')\n rejectToken(new Error('Missing session ID in callback'))\n return\n }\n\n let token: TokenDetails\n try {\n token = await getTokenDetails(tokenUrl.search)\n } catch (err) {\n failLoginRequest('UNRESOLVED_SESSION')\n rejectToken(err instanceof Error ? err : new Error(`Unknown error: ${err}`))\n return\n }\n\n res.writeHead(303, 'See Other', {\n Connection: 'close',\n Location: `${sanityUrl}/login/success`,\n })\n res.end()\n server.close()\n resolveToken(token)\n })\n\n server.on('listening', function onCallbackListen() {\n // Once the server is successfully listening on a port, we can return the promise.\n // We'll then await the _token promise_, while the server is running in the background.\n const callbackUrl = getCallbackUrl(server)\n const loginUrl = getLoginUrl(providerUrl, callbackUrl)\n resolve({loginUrl, server, token: tokenPromise})\n })\n\n server.on('error', function onCallbackServerError(err) {\n // The auth backend accepts any localhost port in the callback origin, so if\n // an explicitly configured port is busy we fall back to an OS-assigned ephemeral port.\n // Port 0 cannot itself be \"in use\" - an EADDRINUSE there means something is\n // genuinely wrong, so only fall back once.\n if ('code' in err && err.code === 'EADDRINUSE' && callbackPort !== 0) {\n debug('Port %d busy, falling back to OS-assigned port', callbackPort)\n callbackPort = 0\n server.listen(callbackPort)\n } else {\n reject(err)\n }\n })\n\n debug('Starting callback server on port %d', callbackPort)\n server.listen(callbackPort)\n })\n}\n\n/**\n * Get the login URL to send the user to for the given auth provider.\n *\n * The generated URL will include a label for the session that includes the\n * hostname and platform of the current computer, to help identify the session.\n *\n * @param providerUrl - The URL of the login provider\n * @param callbackUrl - The callback URL for the local auth token server\n * @returns The login URL\n * @internal\n */\nfunction getLoginUrl(providerUrl: string, callbackUrl: URL): URL {\n // Build a login URL that redirects back back to OAuth flow on success\n const loginUrl = new URL(providerUrl)\n\n // Prefer `MacOS` over `darwin` etc\n const platformName = os.platform()\n const platform = platformName in platformNames ? platformNames[platformName] : platformName\n\n // Prefer `espens-macbook` over `espens-macbook.local`\n const hostname = os.hostname().replaceAll(/\\.(local|lan)$/g, '')\n\n loginUrl.searchParams.set('type', 'token')\n loginUrl.searchParams.set('label', `${hostname} / ${platform}`)\n loginUrl.searchParams.set('origin', callbackUrl.href)\n\n return loginUrl\n}\n\nfunction getCallbackUrl(server: Server): URL {\n const serverUrl = server.address()\n if (!serverUrl || typeof serverUrl === 'string') {\n // Note: `serverUrl` is string only when binding to unix sockets,\n // thus we can safely assume Something Is Wrong™ if it's a string\n throw new Error('Failed to start auth callback server')\n }\n\n return new URL(callbackEndpoint, `http://localhost:${serverUrl.port}`)\n}\n"],"names":["createServer","os","getSanityUrl","subdebug","getTokenDetails","debug","defaultCallbackPort","callbackEndpoint","getPreferredCallbackPort","override","process","env","SANITY_CLI_CALLBACK_PORT","port","Number","parseInt","trim","isNaN","Error","platformNames","aix","android","darwin","freebsd","linux","openbsd","sunos","win32","startServerForTokenCallback","providerUrl","sanityUrl","resolveToken","rejectToken","tokenPromise","Promise","resolve","reject","callbackPort","server","onCallbackServerRequest","req","res","failLoginRequest","code","writeHead","Connection","Location","end","close","url","URL","pathname","write","absoluteTokenUrl","searchParams","get","tokenUrl","has","token","search","err","on","onCallbackListen","callbackUrl","getCallbackUrl","loginUrl","getLoginUrl","onCallbackServerError","listen","platformName","platform","hostname","replaceAll","set","href","serverUrl","address"],"mappings":"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"}
|
|
@@ -320,6 +320,7 @@ export function deployApp(options) {
|
|
|
320
320
|
applicationId: application.id,
|
|
321
321
|
cliConfig,
|
|
322
322
|
created: appCreated,
|
|
323
|
+
createRequested: flags.create,
|
|
323
324
|
organizationId: application.organizationId,
|
|
324
325
|
output,
|
|
325
326
|
title: application.title
|
|
@@ -346,6 +347,7 @@ export function deployApp(options) {
|
|
|
346
347
|
if (dryRun) {
|
|
347
348
|
await checkAppTarget(reporter, {
|
|
348
349
|
appId: getAppId(cliConfig),
|
|
350
|
+
create: flags.create,
|
|
349
351
|
organizationId,
|
|
350
352
|
title
|
|
351
353
|
});
|
|
@@ -356,6 +358,7 @@ export function deployApp(options) {
|
|
|
356
358
|
}
|
|
357
359
|
let application = await findUserApplication({
|
|
358
360
|
cliConfig,
|
|
361
|
+
create: flags.create,
|
|
359
362
|
organizationId,
|
|
360
363
|
output,
|
|
361
364
|
title,
|
|
@@ -440,14 +443,14 @@ async function shipAppDeployment({ application, isAutoUpdating, manifest, source
|
|
|
440
443
|
}
|
|
441
444
|
spin.succeed();
|
|
442
445
|
}
|
|
443
|
-
export function logAppDeployed({ applicationId, cliConfig, created, organizationId, output, title, url = getCoreAppUrl(organizationId, applicationId) }) {
|
|
446
|
+
export function logAppDeployed({ applicationId, cliConfig, created, createRequested = false, organizationId, output, title, url = getCoreAppUrl(organizationId, applicationId) }) {
|
|
444
447
|
const named = title ? ` — "${title}"` : '';
|
|
445
448
|
output.log(`\nSuccess! Application deployed to ${styleText('cyan', url)}${named}`);
|
|
446
449
|
output.log(created ? 'Created a new application.' : 'Updated the existing application.');
|
|
447
450
|
if (getAppId(cliConfig)) return;
|
|
448
451
|
output.log(`\n════ ${styleText('bold', 'Next step:')} ════`);
|
|
449
452
|
if (created) {
|
|
450
|
-
output.log(styleText('yellow', '\
|
|
453
|
+
output.log(styleText('yellow', createRequested ? '\nSave `deployment.appId` and omit --create on later deploys to update this application.' : '\nSave `deployment.appId` to update this application on later deploys.'));
|
|
451
454
|
}
|
|
452
455
|
output.log(styleText('bold', '\nAdd the deployment.appId to your sanity.cli.js or sanity.cli.ts file:'));
|
|
453
456
|
output.log(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/deployApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip} from 'node:zlib'\n\nimport {\n type CoreAppManifest,\n extractCoreAppManifest,\n readIconFromPath,\n resolveTitleUpdate,\n} from '@sanity/cli-build/_internal/manifest'\nimport {type AppVisibility, exitCodes} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {getCoreAppUrl} from '@sanity/cli-core/util'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {\n createCoreApp,\n deployConfig,\n deployWorkbenchApp,\n getApplicationUrl,\n getWorkbench,\n resolveInstallationId,\n resolveWorkbenchConfig,\n summarizeConfig,\n toWorkbenchPayload,\n} from '@sanity/workbench-cli/deploy'\nimport {c as createTar} from 'tar'\n\nimport {\n createDeployment,\n updateUserApplication,\n type UserApplication,\n type UserApplicationResolved,\n} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {EXTERNAL_APP_NOT_SUPPORTED, NO_ORGANIZATION_ID} from '../../util/errorMessages.js'\nimport {buildApp} from '../build/buildApp.js'\nimport {createUserApplication} from './createUserApplication.js'\nimport {\n checkAppId,\n checkAppTarget,\n checkAutoUpdates,\n checkBuild,\n checkPackageVersion,\n type DeployCheckReporter,\n verifyOutputDir,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {listDeploymentFiles, reportInterfaces} from './deploymentPlan.js'\nimport {type DeployPayload, type DeployResult, runDeploy} from './deployRunner.js'\nimport {findUserApplication} from './findUserApplication.js'\nimport {type DeployAppOptions} from './types.js'\n\nconst APP_PACKAGE = '@sanity/sdk-react'\n\nexport function deployApp(options: DeployAppOptions): Promise<void> {\n return runDeploy(options, {\n listFiles: ({projectRoot, sourceDir}) => listDeploymentFiles(sourceDir, projectRoot.directory),\n run: runAppDeployment,\n type: 'coreApp',\n })\n}\n\n/** Validates the deploy, syncs the title from the manifest, and ships the build. */\nasync function runAppDeployment(\n options: DeployAppOptions,\n reporter: DeployCheckReporter,\n): Promise<DeployResult | void> {\n const {cliConfig, flags, output, sourceDir} = options\n const workDir = options.projectRoot.directory\n const organizationId = cliConfig.app?.organizationId\n const appId = getAppId(cliConfig)\n const workbench = getWorkbench(cliConfig)\n // A config (the Media Library) is not an app — it resolves through its own\n // brand and persists to the org's installation instead of hosting an application.\n const config = resolveWorkbenchConfig(cliConfig)\n const dryRun = !!flags['dry-run']\n\n const deployConfigOnly = !!config\n const deployApplication = !config && (!workbench || workbench.hasInterfaces)\n\n const appTitle = workbench\n ? flags.title?.trim() || cliConfig.app?.title?.trim() || workbench.slug\n : ''\n\n // A federated app with no entry, view or service would ship a remote with\n // nothing to load — reported first so it fails before any prompt or API call.\n if (workbench) {\n try {\n workbench.assertDeployable()\n } catch (err) {\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: getErrorMessage(err),\n solution: 'Declare at least one entry, view, or service in the app',\n status: 'fail',\n })\n }\n }\n\n // A config with no fields would overwrite the org's live config with an empty\n // snapshot, so block it here — same \"nothing to deploy\" gate an app gets.\n if (config && config.fields.length === 0) {\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: 'Nothing to deploy: the media library config declares no fields.',\n solution: 'Add at least one field to the media library config',\n status: 'fail',\n })\n }\n\n const isAutoUpdating = checkAutoUpdates(reporter, {cliConfig, flags})\n\n // An application ships the SDK runtime; a media-library config stamps its\n // `sanity` version instead.\n let version: string | null = null\n if (deployApplication) {\n version = await checkPackageVersion(reporter, {moduleName: APP_PACKAGE, workDir})\n } else if (deployConfigOnly) {\n version = await checkPackageVersion(reporter, {moduleName: 'sanity', workDir})\n }\n\n reporter.report(\n organizationId\n ? {message: `Organization: ${organizationId}`, status: 'pass'}\n : {\n message: NO_ORGANIZATION_ID,\n solution: 'Add `app.organizationId` to sanity.cli.ts',\n status: 'fail',\n },\n )\n\n checkAppId(reporter, {cliConfig})\n\n let application: UserApplicationResolved | null = null\n let appCreated = false\n let workbenchApp: object | undefined\n if (flags.external) {\n reporter.report({\n message: EXTERNAL_APP_NOT_SUPPORTED,\n solution: 'Remove the --external flag — apps deploy to Sanity hosting',\n status: 'fail',\n })\n } else if (deployApplication && workbench) {\n workbenchApp = (\n await checkAppTarget(reporter, {\n appId,\n isWorkbenchApp: true,\n organizationId,\n slug: workbench.slug,\n title: appTitle,\n })\n )?.application\n } else if (deployApplication) {\n ;({application, created: appCreated} = await resolveAppApplication(options, {dryRun, reporter}))\n }\n\n // A first deploy mints the app id and the build inlines it; --no-build would\n // ship an existing bundle carrying a different id, so it can't be a first deploy.\n if (deployApplication && workbench && !appId && !flags.external && !flags.build) {\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: 'A first deploy cannot skip the build (--no-build)',\n solution: 'Drop --no-build so the new application id is inlined into the build',\n status: 'fail',\n })\n }\n\n // Read up front so a bad icon path fails before we create or build.\n const appIcon =\n !dryRun && workbench?.icon ? await readIconFromPath(workDir, workbench.icon) : undefined\n\n // Create the app before the build so the bundle carries its real id. A\n // redeploy already has it from `deployment.appId`; a dry run skips creation.\n let applicationId = appId\n let applicationCreated = false\n let rollbackApp: (() => Promise<void>) | undefined\n if (!dryRun && deployApplication && workbench && organizationId && !applicationId) {\n const created = await createCoreApp({\n isSingleton: workbench.isSingleton,\n name: workbench.name,\n organizationId,\n slug: workbench.slug,\n title: appTitle,\n visibility: workbench.visibility,\n })\n workbenchApp = created.application\n applicationId = created.application.id\n rollbackApp = created.rollback\n applicationCreated = true\n }\n\n // A record created above is stranded at its slug (and blocks retries) if any\n // step before it fully deploys fails, so undo the creation on failure.\n try {\n await checkBuild(reporter, {\n build: () =>\n buildApp({\n applicationId: workbench ? applicationId : undefined,\n autoUpdatesEnabled: isAutoUpdating,\n calledFromDeploy: true,\n cliConfig,\n flags,\n outDir: sourceDir,\n output,\n workDir,\n }),\n skipReason: flags.build\n ? undefined\n : 'Build skipped (--no-build) — validating existing output directory',\n successMessage: 'App built',\n })\n\n await verifyOutputDir({\n isWorkbenchApp: workbench !== null || config !== null,\n reporter,\n sourceDir,\n })\n\n // Workbench apps and configs ship a federation remote and don't read the\n // core-app manifest; only plain core-apps do. Manifests aren't strictly\n // essential, so a failure warns and continues.\n let manifest: CoreAppManifest | undefined\n if (!workbench && !config) {\n try {\n manifest = await extractCoreAppManifest({workDir})\n } catch (err) {\n deployDebug('Error extracting app manifest', err)\n reporter.report({\n message: `Error extracting app manifest: ${getErrorMessage(err)}`,\n status: 'warn',\n })\n }\n }\n\n // Resolve the installation in both modes so the report — dry-run and real —\n // shows whether the config is deployable; a missing one fails the deploy here.\n let installationId: string | undefined\n let configSummary: string | undefined\n const configAppType = config?.appType\n if (deployConfigOnly && organizationId && config && configAppType) {\n installationId = await resolveInstallationId({appType: configAppType, organizationId})\n configSummary = summarizeConfig(config)\n reporter.report(\n installationId\n ? {message: configSummary, status: 'pass'}\n : {\n exitCode: exitCodes.USAGE_ERROR,\n message: `No active \"${configAppType}\" installation for organization \"${organizationId}\"`,\n solution:\n 'Install the Media Library for the organization before deploying its config',\n status: 'fail',\n },\n )\n }\n\n const interfaces = deployApplication && workbench ? reportInterfaces(reporter, workbench) : null\n\n // Applied after the app is live (see below) so a failed deploy never leaves\n // the org's installation config without its application.\n const deployApplicationConfig = async (): Promise<void> => {\n if (installationId && version && configAppType && organizationId) {\n await deployConfig({\n appType: configAppType,\n installationId,\n organizationId,\n output,\n sourceDir,\n version,\n })\n }\n }\n\n const payload: DeployPayload = {\n appId: appId ?? null,\n isAutoUpdating,\n ...(organizationId ? {organizationId} : {}),\n type: 'coreApp',\n version,\n ...toWorkbenchPayload(workbench, {config: configSummary, interfaces, title: appTitle}),\n ...(configSummary ? {config: configSummary} : {}),\n }\n\n // Dry run stops here — everything below mutates.\n if (dryRun) return {application: null, payload}\n\n // A config-only singleton ships no application, only its config.\n if (!deployApplication) {\n await deployApplicationConfig()\n if (installationId && version) {\n return {application: null, installationId, payload}\n }\n return\n }\n\n // A real deploy already exited on a version-resolution failure; this narrows the type.\n if (!version) return\n\n // The app was created (or resolved from `deployment.appId`) before the build,\n // so this only ships the deployment; plain coreApps use user-applications below.\n if (workbench && organizationId && applicationId) {\n await deployWorkbenchApp({\n app: cliConfig.app,\n applicationId,\n icon: appIcon,\n isApp: true,\n isAutoUpdating,\n // Once the deployment is live, a metadata-sync or later config failure\n // must not delete the app.\n onDeployed: () => {\n rollbackApp = undefined\n },\n slug: workbench.slug,\n sourceDir,\n title: appTitle,\n version,\n visibility: workbench.visibility,\n })\n await deployApplicationConfig()\n const url = getApplicationUrl({id: applicationId, organizationId, type: 'coreApp'})\n logAppDeployed({\n applicationId,\n cliConfig,\n created: applicationCreated,\n organizationId,\n output,\n title: appTitle,\n url,\n })\n return {\n action: applicationCreated ? 'create' : 'update',\n application: workbenchApp ?? null,\n payload,\n url,\n }\n }\n\n // A real deploy has already exited if anything failed; landing here without a\n // resolved application means the deploy target was never resolved.\n if (!application) return\n\n application = await syncApplicationMetadata({\n application,\n manifest,\n output,\n visibility: cliConfig.app?.visibility,\n })\n await shipAppDeployment({application, isAutoUpdating, manifest, sourceDir, version})\n logAppDeployed({\n applicationId: application.id,\n cliConfig,\n created: appCreated,\n organizationId: application.organizationId,\n output,\n title: application.title,\n })\n return {\n action: appCreated ? 'create' : 'update',\n application,\n payload,\n url: getCoreAppUrl(application.organizationId, application.id),\n }\n } catch (err) {\n await rollbackApp?.()\n throw err\n }\n}\n\n/**\n * Finds the application a real deploy targets, creating one when none is\n * configured. A dry run resolves and reports the target read-only instead.\n */\nasync function resolveAppApplication(\n options: DeployAppOptions,\n {dryRun, reporter}: {dryRun: boolean; reporter: DeployCheckReporter},\n): Promise<{application: UserApplicationResolved | null; created: boolean}> {\n const {cliConfig, flags, output} = options\n const organizationId = cliConfig.app?.organizationId ?? ''\n // Create name from --title or `app.title` config; blank falls back to the prompt\n const title = flags.title?.trim() || cliConfig.app?.title?.trim() || undefined\n\n if (dryRun) {\n await checkAppTarget(reporter, {appId: getAppId(cliConfig), organizationId, title})\n return {application: null, created: false}\n }\n\n let application = await findUserApplication({\n cliConfig,\n organizationId,\n output,\n title,\n unattended: !!flags.yes,\n })\n deployDebug('User application found', application)\n\n if (!application) {\n deployDebug('No user application found. Creating a new one')\n application = await createUserApplication(organizationId, title, cliConfig.app?.visibility)\n deployDebug('User application created', application)\n return {application, created: true}\n }\n\n return {application, created: false}\n}\n\n/**\n * Syncs application metadata on redeploy when it has changed: the title from the\n * manifest and the dashboard visibility from config. Sends a single PATCH with\n * only the changed fields, and skips the request entirely when nothing changed.\n */\nexport async function syncApplicationMetadata({\n application,\n manifest,\n output,\n visibility,\n}: {\n application: UserApplicationResolved\n manifest: CoreAppManifest | undefined\n output: DeployAppOptions['output']\n visibility: AppVisibility | undefined\n}): Promise<UserApplicationResolved> {\n const titleUpdate = resolveTitleUpdate(manifest, application)\n // Treat an unset server value as `default` so a config of `default` is a no-op.\n const visibilityChanged =\n visibility !== undefined && visibility !== (application.dashboardStatus ?? 'default')\n\n if (!titleUpdate && !visibilityChanged) return application\n\n if (titleUpdate) {\n deployDebug('Updating application title from manifest', titleUpdate)\n output.log(\n titleUpdate.from\n ? `Updating title from \"${titleUpdate.from}\" to \"${titleUpdate.to}\"`\n : `Setting application title to \"${titleUpdate.to}\"`,\n )\n }\n if (visibilityChanged) {\n output.log(`Setting dashboard visibility to \"${visibility}\"`)\n }\n\n const spin = spinner('Updating application').start()\n try {\n const updated = await updateUserApplication({\n applicationId: application.id,\n appType: 'coreApp',\n body: {\n ...(titleUpdate ? {title: titleUpdate.to} : {}),\n ...(visibilityChanged ? {dashboardStatus: visibility} : {}),\n },\n })\n spin.succeed()\n return updated\n } catch (err) {\n spin.fail()\n const message = getErrorMessage(err)\n deployDebug('Error updating application metadata', {message})\n output.warn(`Error updating application metadata: ${message}`)\n return application\n }\n}\n\nasync function shipAppDeployment({\n application,\n isAutoUpdating,\n manifest,\n sourceDir,\n version,\n}: {\n application: UserApplication\n isAutoUpdating: boolean\n manifest: CoreAppManifest | undefined\n sourceDir: string\n version: string\n}): Promise<void> {\n const tarball = createTar({cwd: dirname(sourceDir)}, [basename(sourceDir)]).pipe(createGzip())\n\n const spin = spinner('Deploying...').start()\n try {\n await createDeployment({\n applicationId: application.id,\n isApp: true,\n isAutoUpdating,\n manifest,\n tarball,\n version,\n })\n } catch (error) {\n spin.clear()\n throw error\n }\n spin.succeed()\n}\n\nexport function logAppDeployed({\n applicationId,\n cliConfig,\n created,\n organizationId,\n output,\n title,\n url = getCoreAppUrl(organizationId, applicationId),\n}: {\n applicationId: string\n cliConfig: DeployAppOptions['cliConfig']\n created: boolean\n organizationId: string\n output: DeployAppOptions['output']\n title: string | null\n url?: string\n}): void {\n const named = title ? ` — \"${title}\"` : ''\n output.log(`\\nSuccess! Application deployed to ${styleText('cyan', url)}${named}`)\n output.log(created ? 'Created a new application.' : 'Updated the existing application.')\n\n if (getAppId(cliConfig)) return\n\n output.log(`\\n════ ${styleText('bold', 'Next step:')} ════`)\n if (created) {\n output.log(\n styleText(\n 'yellow',\n '\\nDeploying again without `deployment.appId` creates another new application.',\n ),\n )\n }\n output.log(\n styleText('bold', '\\nAdd the deployment.appId to your sanity.cli.js or sanity.cli.ts file:'),\n )\n output.log(`\n${styleText(\n 'dim',\n `app: {\n // your application config here…\n}`,\n)},\n${styleText(\n ['bold', 'green'],\n `deployment: {\n appId: '${applicationId}',\n}\\n`,\n)}`)\n}\n"],"names":["basename","dirname","styleText","createGzip","extractCoreAppManifest","readIconFromPath","resolveTitleUpdate","exitCodes","getErrorMessage","getCoreAppUrl","spinner","createCoreApp","deployConfig","deployWorkbenchApp","getApplicationUrl","getWorkbench","resolveInstallationId","resolveWorkbenchConfig","summarizeConfig","toWorkbenchPayload","c","createTar","createDeployment","updateUserApplication","getAppId","EXTERNAL_APP_NOT_SUPPORTED","NO_ORGANIZATION_ID","buildApp","createUserApplication","checkAppId","checkAppTarget","checkAutoUpdates","checkBuild","checkPackageVersion","verifyOutputDir","deployDebug","listDeploymentFiles","reportInterfaces","runDeploy","findUserApplication","APP_PACKAGE","deployApp","options","listFiles","projectRoot","sourceDir","directory","run","runAppDeployment","type","reporter","cliConfig","flags","output","workDir","organizationId","app","appId","workbench","config","dryRun","deployConfigOnly","deployApplication","hasInterfaces","appTitle","title","trim","slug","assertDeployable","err","report","exitCode","USAGE_ERROR","message","solution","status","fields","length","isAutoUpdating","version","moduleName","application","appCreated","workbenchApp","external","isWorkbenchApp","created","resolveAppApplication","build","appIcon","icon","undefined","applicationId","applicationCreated","rollbackApp","isSingleton","name","visibility","id","rollback","autoUpdatesEnabled","calledFromDeploy","outDir","skipReason","successMessage","manifest","installationId","configSummary","configAppType","appType","interfaces","deployApplicationConfig","payload","isApp","onDeployed","url","logAppDeployed","action","syncApplicationMetadata","shipAppDeployment","unattended","yes","titleUpdate","visibilityChanged","dashboardStatus","log","from","to","spin","start","updated","body","succeed","fail","warn","tarball","cwd","pipe","error","clear","named"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/deployApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip} from 'node:zlib'\n\nimport {\n type CoreAppManifest,\n extractCoreAppManifest,\n readIconFromPath,\n resolveTitleUpdate,\n} from '@sanity/cli-build/_internal/manifest'\nimport {type AppVisibility, exitCodes} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {getCoreAppUrl} from '@sanity/cli-core/util'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {\n createCoreApp,\n deployConfig,\n deployWorkbenchApp,\n getApplicationUrl,\n getWorkbench,\n resolveInstallationId,\n resolveWorkbenchConfig,\n summarizeConfig,\n toWorkbenchPayload,\n} from '@sanity/workbench-cli/deploy'\nimport {c as createTar} from 'tar'\n\nimport {\n createDeployment,\n updateUserApplication,\n type UserApplication,\n type UserApplicationResolved,\n} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {EXTERNAL_APP_NOT_SUPPORTED, NO_ORGANIZATION_ID} from '../../util/errorMessages.js'\nimport {buildApp} from '../build/buildApp.js'\nimport {createUserApplication} from './createUserApplication.js'\nimport {\n checkAppId,\n checkAppTarget,\n checkAutoUpdates,\n checkBuild,\n checkPackageVersion,\n type DeployCheckReporter,\n verifyOutputDir,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {listDeploymentFiles, reportInterfaces} from './deploymentPlan.js'\nimport {type DeployPayload, type DeployResult, runDeploy} from './deployRunner.js'\nimport {findUserApplication} from './findUserApplication.js'\nimport {type DeployAppOptions} from './types.js'\n\nconst APP_PACKAGE = '@sanity/sdk-react'\n\nexport function deployApp(options: DeployAppOptions): Promise<void> {\n return runDeploy(options, {\n listFiles: ({projectRoot, sourceDir}) => listDeploymentFiles(sourceDir, projectRoot.directory),\n run: runAppDeployment,\n type: 'coreApp',\n })\n}\n\n/** Validates the deploy, syncs the title from the manifest, and ships the build. */\nasync function runAppDeployment(\n options: DeployAppOptions,\n reporter: DeployCheckReporter,\n): Promise<DeployResult | void> {\n const {cliConfig, flags, output, sourceDir} = options\n const workDir = options.projectRoot.directory\n const organizationId = cliConfig.app?.organizationId\n const appId = getAppId(cliConfig)\n const workbench = getWorkbench(cliConfig)\n // A config (the Media Library) is not an app — it resolves through its own\n // brand and persists to the org's installation instead of hosting an application.\n const config = resolveWorkbenchConfig(cliConfig)\n const dryRun = !!flags['dry-run']\n\n const deployConfigOnly = !!config\n const deployApplication = !config && (!workbench || workbench.hasInterfaces)\n\n const appTitle = workbench\n ? flags.title?.trim() || cliConfig.app?.title?.trim() || workbench.slug\n : ''\n\n // A federated app with no entry, view or service would ship a remote with\n // nothing to load — reported first so it fails before any prompt or API call.\n if (workbench) {\n try {\n workbench.assertDeployable()\n } catch (err) {\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: getErrorMessage(err),\n solution: 'Declare at least one entry, view, or service in the app',\n status: 'fail',\n })\n }\n }\n\n // A config with no fields would overwrite the org's live config with an empty\n // snapshot, so block it here — same \"nothing to deploy\" gate an app gets.\n if (config && config.fields.length === 0) {\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: 'Nothing to deploy: the media library config declares no fields.',\n solution: 'Add at least one field to the media library config',\n status: 'fail',\n })\n }\n\n const isAutoUpdating = checkAutoUpdates(reporter, {cliConfig, flags})\n\n // An application ships the SDK runtime; a media-library config stamps its\n // `sanity` version instead.\n let version: string | null = null\n if (deployApplication) {\n version = await checkPackageVersion(reporter, {moduleName: APP_PACKAGE, workDir})\n } else if (deployConfigOnly) {\n version = await checkPackageVersion(reporter, {moduleName: 'sanity', workDir})\n }\n\n reporter.report(\n organizationId\n ? {message: `Organization: ${organizationId}`, status: 'pass'}\n : {\n message: NO_ORGANIZATION_ID,\n solution: 'Add `app.organizationId` to sanity.cli.ts',\n status: 'fail',\n },\n )\n\n checkAppId(reporter, {cliConfig})\n\n let application: UserApplicationResolved | null = null\n let appCreated = false\n let workbenchApp: object | undefined\n if (flags.external) {\n reporter.report({\n message: EXTERNAL_APP_NOT_SUPPORTED,\n solution: 'Remove the --external flag — apps deploy to Sanity hosting',\n status: 'fail',\n })\n } else if (deployApplication && workbench) {\n workbenchApp = (\n await checkAppTarget(reporter, {\n appId,\n isWorkbenchApp: true,\n organizationId,\n slug: workbench.slug,\n title: appTitle,\n })\n )?.application\n } else if (deployApplication) {\n ;({application, created: appCreated} = await resolveAppApplication(options, {dryRun, reporter}))\n }\n\n // A first deploy mints the app id and the build inlines it; --no-build would\n // ship an existing bundle carrying a different id, so it can't be a first deploy.\n if (deployApplication && workbench && !appId && !flags.external && !flags.build) {\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: 'A first deploy cannot skip the build (--no-build)',\n solution: 'Drop --no-build so the new application id is inlined into the build',\n status: 'fail',\n })\n }\n\n // Read up front so a bad icon path fails before we create or build.\n const appIcon =\n !dryRun && workbench?.icon ? await readIconFromPath(workDir, workbench.icon) : undefined\n\n // Create the app before the build so the bundle carries its real id. A\n // redeploy already has it from `deployment.appId`; a dry run skips creation.\n let applicationId = appId\n let applicationCreated = false\n let rollbackApp: (() => Promise<void>) | undefined\n if (!dryRun && deployApplication && workbench && organizationId && !applicationId) {\n const created = await createCoreApp({\n isSingleton: workbench.isSingleton,\n name: workbench.name,\n organizationId,\n slug: workbench.slug,\n title: appTitle,\n visibility: workbench.visibility,\n })\n workbenchApp = created.application\n applicationId = created.application.id\n rollbackApp = created.rollback\n applicationCreated = true\n }\n\n // A record created above is stranded at its slug (and blocks retries) if any\n // step before it fully deploys fails, so undo the creation on failure.\n try {\n await checkBuild(reporter, {\n build: () =>\n buildApp({\n applicationId: workbench ? applicationId : undefined,\n autoUpdatesEnabled: isAutoUpdating,\n calledFromDeploy: true,\n cliConfig,\n flags,\n outDir: sourceDir,\n output,\n workDir,\n }),\n skipReason: flags.build\n ? undefined\n : 'Build skipped (--no-build) — validating existing output directory',\n successMessage: 'App built',\n })\n\n await verifyOutputDir({\n isWorkbenchApp: workbench !== null || config !== null,\n reporter,\n sourceDir,\n })\n\n // Workbench apps and configs ship a federation remote and don't read the\n // core-app manifest; only plain core-apps do. Manifests aren't strictly\n // essential, so a failure warns and continues.\n let manifest: CoreAppManifest | undefined\n if (!workbench && !config) {\n try {\n manifest = await extractCoreAppManifest({workDir})\n } catch (err) {\n deployDebug('Error extracting app manifest', err)\n reporter.report({\n message: `Error extracting app manifest: ${getErrorMessage(err)}`,\n status: 'warn',\n })\n }\n }\n\n // Resolve the installation in both modes so the report — dry-run and real —\n // shows whether the config is deployable; a missing one fails the deploy here.\n let installationId: string | undefined\n let configSummary: string | undefined\n const configAppType = config?.appType\n if (deployConfigOnly && organizationId && config && configAppType) {\n installationId = await resolveInstallationId({appType: configAppType, organizationId})\n configSummary = summarizeConfig(config)\n reporter.report(\n installationId\n ? {message: configSummary, status: 'pass'}\n : {\n exitCode: exitCodes.USAGE_ERROR,\n message: `No active \"${configAppType}\" installation for organization \"${organizationId}\"`,\n solution:\n 'Install the Media Library for the organization before deploying its config',\n status: 'fail',\n },\n )\n }\n\n const interfaces = deployApplication && workbench ? reportInterfaces(reporter, workbench) : null\n\n // Applied after the app is live (see below) so a failed deploy never leaves\n // the org's installation config without its application.\n const deployApplicationConfig = async (): Promise<void> => {\n if (installationId && version && configAppType && organizationId) {\n await deployConfig({\n appType: configAppType,\n installationId,\n organizationId,\n output,\n sourceDir,\n version,\n })\n }\n }\n\n const payload: DeployPayload = {\n appId: appId ?? null,\n isAutoUpdating,\n ...(organizationId ? {organizationId} : {}),\n type: 'coreApp',\n version,\n ...toWorkbenchPayload(workbench, {config: configSummary, interfaces, title: appTitle}),\n ...(configSummary ? {config: configSummary} : {}),\n }\n\n // Dry run stops here — everything below mutates.\n if (dryRun) return {application: null, payload}\n\n // A config-only singleton ships no application, only its config.\n if (!deployApplication) {\n await deployApplicationConfig()\n if (installationId && version) {\n return {application: null, installationId, payload}\n }\n return\n }\n\n // A real deploy already exited on a version-resolution failure; this narrows the type.\n if (!version) return\n\n // The app was created (or resolved from `deployment.appId`) before the build,\n // so this only ships the deployment; plain coreApps use user-applications below.\n if (workbench && organizationId && applicationId) {\n await deployWorkbenchApp({\n app: cliConfig.app,\n applicationId,\n icon: appIcon,\n isApp: true,\n isAutoUpdating,\n // Once the deployment is live, a metadata-sync or later config failure\n // must not delete the app.\n onDeployed: () => {\n rollbackApp = undefined\n },\n slug: workbench.slug,\n sourceDir,\n title: appTitle,\n version,\n visibility: workbench.visibility,\n })\n await deployApplicationConfig()\n const url = getApplicationUrl({id: applicationId, organizationId, type: 'coreApp'})\n logAppDeployed({\n applicationId,\n cliConfig,\n created: applicationCreated,\n organizationId,\n output,\n title: appTitle,\n url,\n })\n return {\n action: applicationCreated ? 'create' : 'update',\n application: workbenchApp ?? null,\n payload,\n url,\n }\n }\n\n // A real deploy has already exited if anything failed; landing here without a\n // resolved application means the deploy target was never resolved.\n if (!application) return\n\n application = await syncApplicationMetadata({\n application,\n manifest,\n output,\n visibility: cliConfig.app?.visibility,\n })\n await shipAppDeployment({application, isAutoUpdating, manifest, sourceDir, version})\n logAppDeployed({\n applicationId: application.id,\n cliConfig,\n created: appCreated,\n createRequested: flags.create,\n organizationId: application.organizationId,\n output,\n title: application.title,\n })\n return {\n action: appCreated ? 'create' : 'update',\n application,\n payload,\n url: getCoreAppUrl(application.organizationId, application.id),\n }\n } catch (err) {\n await rollbackApp?.()\n throw err\n }\n}\n\n/**\n * Finds the application a real deploy targets, creating one when none is\n * configured. A dry run resolves and reports the target read-only instead.\n */\nasync function resolveAppApplication(\n options: DeployAppOptions,\n {dryRun, reporter}: {dryRun: boolean; reporter: DeployCheckReporter},\n): Promise<{application: UserApplicationResolved | null; created: boolean}> {\n const {cliConfig, flags, output} = options\n const organizationId = cliConfig.app?.organizationId ?? ''\n // Create name from --title or `app.title` config; blank falls back to the prompt\n const title = flags.title?.trim() || cliConfig.app?.title?.trim() || undefined\n\n if (dryRun) {\n await checkAppTarget(reporter, {\n appId: getAppId(cliConfig),\n create: flags.create,\n organizationId,\n title,\n })\n return {application: null, created: false}\n }\n\n let application = await findUserApplication({\n cliConfig,\n create: flags.create,\n organizationId,\n output,\n title,\n unattended: !!flags.yes,\n })\n deployDebug('User application found', application)\n\n if (!application) {\n deployDebug('No user application found. Creating a new one')\n application = await createUserApplication(organizationId, title, cliConfig.app?.visibility)\n deployDebug('User application created', application)\n return {application, created: true}\n }\n\n return {application, created: false}\n}\n\n/**\n * Syncs application metadata on redeploy when it has changed: the title from the\n * manifest and the dashboard visibility from config. Sends a single PATCH with\n * only the changed fields, and skips the request entirely when nothing changed.\n */\nexport async function syncApplicationMetadata({\n application,\n manifest,\n output,\n visibility,\n}: {\n application: UserApplicationResolved\n manifest: CoreAppManifest | undefined\n output: DeployAppOptions['output']\n visibility: AppVisibility | undefined\n}): Promise<UserApplicationResolved> {\n const titleUpdate = resolveTitleUpdate(manifest, application)\n // Treat an unset server value as `default` so a config of `default` is a no-op.\n const visibilityChanged =\n visibility !== undefined && visibility !== (application.dashboardStatus ?? 'default')\n\n if (!titleUpdate && !visibilityChanged) return application\n\n if (titleUpdate) {\n deployDebug('Updating application title from manifest', titleUpdate)\n output.log(\n titleUpdate.from\n ? `Updating title from \"${titleUpdate.from}\" to \"${titleUpdate.to}\"`\n : `Setting application title to \"${titleUpdate.to}\"`,\n )\n }\n if (visibilityChanged) {\n output.log(`Setting dashboard visibility to \"${visibility}\"`)\n }\n\n const spin = spinner('Updating application').start()\n try {\n const updated = await updateUserApplication({\n applicationId: application.id,\n appType: 'coreApp',\n body: {\n ...(titleUpdate ? {title: titleUpdate.to} : {}),\n ...(visibilityChanged ? {dashboardStatus: visibility} : {}),\n },\n })\n spin.succeed()\n return updated\n } catch (err) {\n spin.fail()\n const message = getErrorMessage(err)\n deployDebug('Error updating application metadata', {message})\n output.warn(`Error updating application metadata: ${message}`)\n return application\n }\n}\n\nasync function shipAppDeployment({\n application,\n isAutoUpdating,\n manifest,\n sourceDir,\n version,\n}: {\n application: UserApplication\n isAutoUpdating: boolean\n manifest: CoreAppManifest | undefined\n sourceDir: string\n version: string\n}): Promise<void> {\n const tarball = createTar({cwd: dirname(sourceDir)}, [basename(sourceDir)]).pipe(createGzip())\n\n const spin = spinner('Deploying...').start()\n try {\n await createDeployment({\n applicationId: application.id,\n isApp: true,\n isAutoUpdating,\n manifest,\n tarball,\n version,\n })\n } catch (error) {\n spin.clear()\n throw error\n }\n spin.succeed()\n}\n\nexport function logAppDeployed({\n applicationId,\n cliConfig,\n created,\n createRequested = false,\n organizationId,\n output,\n title,\n url = getCoreAppUrl(organizationId, applicationId),\n}: {\n applicationId: string\n cliConfig: DeployAppOptions['cliConfig']\n created: boolean\n createRequested?: boolean\n organizationId: string\n output: DeployAppOptions['output']\n title: string | null\n url?: string\n}): void {\n const named = title ? ` — \"${title}\"` : ''\n output.log(`\\nSuccess! Application deployed to ${styleText('cyan', url)}${named}`)\n output.log(created ? 'Created a new application.' : 'Updated the existing application.')\n\n if (getAppId(cliConfig)) return\n\n output.log(`\\n════ ${styleText('bold', 'Next step:')} ════`)\n if (created) {\n output.log(\n styleText(\n 'yellow',\n createRequested\n ? '\\nSave `deployment.appId` and omit --create on later deploys to update this application.'\n : '\\nSave `deployment.appId` to update this application on later deploys.',\n ),\n )\n }\n output.log(\n styleText('bold', '\\nAdd the deployment.appId to your sanity.cli.js or sanity.cli.ts file:'),\n )\n output.log(`\n${styleText(\n 'dim',\n `app: {\n // your application config here…\n}`,\n)},\n${styleText(\n ['bold', 'green'],\n `deployment: {\n appId: '${applicationId}',\n}\\n`,\n)}`)\n}\n"],"names":["basename","dirname","styleText","createGzip","extractCoreAppManifest","readIconFromPath","resolveTitleUpdate","exitCodes","getErrorMessage","getCoreAppUrl","spinner","createCoreApp","deployConfig","deployWorkbenchApp","getApplicationUrl","getWorkbench","resolveInstallationId","resolveWorkbenchConfig","summarizeConfig","toWorkbenchPayload","c","createTar","createDeployment","updateUserApplication","getAppId","EXTERNAL_APP_NOT_SUPPORTED","NO_ORGANIZATION_ID","buildApp","createUserApplication","checkAppId","checkAppTarget","checkAutoUpdates","checkBuild","checkPackageVersion","verifyOutputDir","deployDebug","listDeploymentFiles","reportInterfaces","runDeploy","findUserApplication","APP_PACKAGE","deployApp","options","listFiles","projectRoot","sourceDir","directory","run","runAppDeployment","type","reporter","cliConfig","flags","output","workDir","organizationId","app","appId","workbench","config","dryRun","deployConfigOnly","deployApplication","hasInterfaces","appTitle","title","trim","slug","assertDeployable","err","report","exitCode","USAGE_ERROR","message","solution","status","fields","length","isAutoUpdating","version","moduleName","application","appCreated","workbenchApp","external","isWorkbenchApp","created","resolveAppApplication","build","appIcon","icon","undefined","applicationId","applicationCreated","rollbackApp","isSingleton","name","visibility","id","rollback","autoUpdatesEnabled","calledFromDeploy","outDir","skipReason","successMessage","manifest","installationId","configSummary","configAppType","appType","interfaces","deployApplicationConfig","payload","isApp","onDeployed","url","logAppDeployed","action","syncApplicationMetadata","shipAppDeployment","createRequested","create","unattended","yes","titleUpdate","visibilityChanged","dashboardStatus","log","from","to","spin","start","updated","body","succeed","fail","warn","tarball","cwd","pipe","error","clear","named"],"mappings":"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"}
|
|
@@ -149,7 +149,7 @@ export async function checkBuild(reporter, { build, skipReason, successMessage }
|
|
|
149
149
|
return {
|
|
150
150
|
exitCode: exitCodes.USAGE_ERROR,
|
|
151
151
|
message: `No \`deployment.appId\` configured (${resolution.existing.length} existing ${resolution.existing.length === 1 ? 'application' : 'applications'} to choose from)`,
|
|
152
|
-
solution: '
|
|
152
|
+
solution: 'Set `deployment.appId` in sanity.cli.ts to deploy to an existing app, or run `sanity deploy --create --title "<name>" --yes` to create a new one',
|
|
153
153
|
status: 'fail'
|
|
154
154
|
};
|
|
155
155
|
}
|
|
@@ -230,7 +230,7 @@ export function describeAppTargetError(err, organizationId) {
|
|
|
230
230
|
}
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
|
-
const { appId, organizationId } = options;
|
|
233
|
+
const { appId, create, organizationId } = options;
|
|
234
234
|
return runStep(reporter, {
|
|
235
235
|
debug: deployDebug,
|
|
236
236
|
formatError: (err)=>describeAppTargetError(err, organizationId),
|
|
@@ -238,6 +238,7 @@ export function describeAppTargetError(err, organizationId) {
|
|
|
238
238
|
work: async ()=>{
|
|
239
239
|
const check = describeAppTarget(await resolveAppDeployTarget({
|
|
240
240
|
appId,
|
|
241
|
+
create,
|
|
241
242
|
organizationId
|
|
242
243
|
}), {
|
|
243
244
|
title
|