@sanity/cli 7.4.2 → 7.6.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/dist/actions/build/buildApp.js +7 -183
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStudio.js +6 -213
- package/dist/actions/build/buildStudio.js.map +1 -1
- package/dist/actions/build/shouldAutoUpdate.js +100 -22
- package/dist/actions/build/shouldAutoUpdate.js.map +1 -1
- package/dist/actions/deploy/{createStudioUserApplication.js → createUserApplication.js} +56 -5
- package/dist/actions/deploy/createUserApplication.js.map +1 -0
- package/dist/actions/deploy/deployApp.js +232 -185
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployChecks.js +263 -0
- package/dist/actions/deploy/deployChecks.js.map +1 -0
- package/dist/actions/deploy/deployRunner.js +57 -0
- package/dist/actions/deploy/deployRunner.js.map +1 -0
- package/dist/actions/deploy/deployStudio.js +189 -198
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/deploymentPlan.js +89 -0
- package/dist/actions/deploy/deploymentPlan.js.map +1 -0
- package/dist/actions/deploy/findUserApplication.js +209 -0
- package/dist/actions/deploy/findUserApplication.js.map +1 -0
- package/dist/actions/deploy/resolveDeployTarget.js +185 -0
- package/dist/actions/deploy/resolveDeployTarget.js.map +1 -0
- package/dist/actions/dev/devAction.js +1 -1
- package/dist/actions/dev/devAction.js.map +1 -1
- package/dist/actions/dev/servers/getDevServerConfig.js +18 -6
- package/dist/actions/dev/servers/getDevServerConfig.js.map +1 -1
- package/dist/actions/dev/servers/startAppDevServer.js +1 -1
- package/dist/actions/dev/servers/startAppDevServer.js.map +1 -1
- package/dist/actions/dev/servers/startStudioDevServer.js +2 -1
- package/dist/actions/dev/servers/startStudioDevServer.js.map +1 -1
- package/dist/actions/manifest/extractCoreAppManifest.js +14 -0
- package/dist/actions/manifest/extractCoreAppManifest.js.map +1 -1
- package/dist/actions/mcp/editorConfigs.js +11 -5
- package/dist/actions/mcp/editorConfigs.js.map +1 -1
- package/dist/commands/deploy.js +27 -11
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +2 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.js +2 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/exports/index.d.ts +9 -0
- package/dist/exports/index.js +1 -8
- package/dist/exports/index.js.map +1 -1
- package/dist/server/devServer.js +27 -3
- package/dist/server/devServer.js.map +1 -1
- package/dist/util/appId.js +13 -5
- package/dist/util/appId.js.map +1 -1
- package/dist/util/compareDependencyVersions.js.map +1 -1
- package/dist/util/determineIsApp.js +1 -1
- package/dist/util/determineIsApp.js.map +1 -1
- package/dist/util/errorMessages.js +2 -0
- package/dist/util/errorMessages.js.map +1 -1
- package/oclif.manifest.json +14 -1
- package/package.json +8 -8
- package/templates/app-quickstart/.claude/skills/sanity-app-sdk/SKILL.md +56 -0
- package/templates/app-quickstart/AGENTS.md +40 -0
- package/templates/app-quickstart/README.md +22 -0
- package/templates/app-quickstart/src/ExampleComponent.css +6 -0
- package/templates/app-quickstart/src/ExampleComponent.tsx +30 -9
- package/templates/app-sanity-ui/.claude/skills/sanity-app-sdk/SKILL.md +60 -0
- package/templates/app-sanity-ui/AGENTS.md +44 -0
- package/templates/app-sanity-ui/README.md +23 -0
- package/templates/app-sanity-ui/src/ExampleComponent.tsx +8 -5
- package/dist/actions/build/handlePrereleaseVersions.js +0 -44
- package/dist/actions/build/handlePrereleaseVersions.js.map +0 -1
- package/dist/actions/deploy/createStudioUserApplication.js.map +0 -1
- package/dist/actions/deploy/createUserApplicationForApp.js +0 -56
- package/dist/actions/deploy/createUserApplicationForApp.js.map +0 -1
- package/dist/actions/deploy/findUserApplicationForApp.js +0 -111
- package/dist/actions/deploy/findUserApplicationForApp.js.map +0 -1
- package/dist/actions/deploy/findUserApplicationForStudio.js +0 -172
- package/dist/actions/deploy/findUserApplicationForStudio.js.map +0 -1
- package/dist/actions/deploy/viewDeployment.js +0 -32
- package/dist/actions/deploy/viewDeployment.js.map +0 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working in this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
A React application built with the Sanity App SDK (`@sanity/sdk-react`) and Sanity UI (`@sanity/ui`). It is not a Sanity Studio. The app reads and writes content in a Sanity project through SDK hooks, and runs inside the organization's Sanity Dashboard, in development and when deployed. The `sanity` CLI runs it with Vite under the hood.
|
|
8
|
+
|
|
9
|
+
## Key files
|
|
10
|
+
|
|
11
|
+
- `src/App.tsx`: entry point. The `SanityApp` component takes a `config` array with `projectId` and `dataset`. All SDK hooks must be used inside `SanityApp`.
|
|
12
|
+
- `src/SanityUI.tsx`: wraps the app in Sanity UI's `ThemeProvider` with a theme from `buildTheme()`. Sanity UI components must render inside this provider.
|
|
13
|
+
- `sanity.cli.ts`: CLI config with the organization ID and app entry path.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
- `npm run dev`: starts the dev server on port 3333, but the app only renders inside the Sanity Dashboard. The CLI prints a Dashboard URL to open. Viewing it requires a signed-in Sanity account, so a human must complete authentication in the browser.
|
|
18
|
+
- `npm run build`: production build.
|
|
19
|
+
- `npm run deploy`: deploy to the Sanity Dashboard.
|
|
20
|
+
|
|
21
|
+
Environment variables prefixed with `SANITY_APP_` are bundled into the app.
|
|
22
|
+
|
|
23
|
+
## Working with the App SDK
|
|
24
|
+
|
|
25
|
+
If the Sanity MCP server is available, call its `get_sanity_rules` tool with the `app-sdk` rule before writing SDK code. That rule is the maintained guide and supersedes the notes below.
|
|
26
|
+
|
|
27
|
+
Essentials:
|
|
28
|
+
|
|
29
|
+
- Data hooks suspend while loading. Wrap every data-fetching component in `<Suspense>`, keep one fetching hook per component, and always pass a `fallback` to `SanityApp`.
|
|
30
|
+
- Fetch lists with `useDocuments` (or `usePaginatedDocuments`). They return document handles, not full documents. Spread a handle into `useDocumentProjection` to display fields, or into `useDocument` and `useEditDocument` for real-time editing.
|
|
31
|
+
- Use `documentId` as the React key when rendering document lists, never the array index.
|
|
32
|
+
- Do not hold document field values in `useState` and save on submit. Write through `useEditDocument` on change so content stays in sync with the Content Lake.
|
|
33
|
+
- Prefer handles plus projections over raw GROQ. Reach for `useQuery` only when a complex query genuinely needs it.
|
|
34
|
+
- Build UI with Sanity UI primitives like `Card`, `Stack`, `Flex`, `Text`, and `Button` for a look consistent with Sanity tooling.
|
|
35
|
+
|
|
36
|
+
## Documentation
|
|
37
|
+
|
|
38
|
+
- App SDK docs: https://www.sanity.io/docs/app-sdk
|
|
39
|
+
- Best practices: https://www.sanity.io/docs/app-sdk/sdk-best-practices
|
|
40
|
+
- Editing documents: https://www.sanity.io/docs/app-sdk/editing-documents
|
|
41
|
+
- Configuration: https://www.sanity.io/docs/app-sdk/sdk-configuration
|
|
42
|
+
- Sanity UI with the App SDK: https://www.sanity.io/docs/app-sdk/sanity-ui-sdk
|
|
43
|
+
- Sanity UI docs: https://www.sanity.io/ui
|
|
44
|
+
- API reference with current signatures: https://reference.sanity.io/_sanity/sdk-react/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Sanity App
|
|
2
|
+
|
|
3
|
+
A custom application built with the [Sanity App SDK](https://www.sanity.io/docs/app-sdk?utm_source=readme) and [Sanity UI](https://www.sanity.io/ui?utm_source=readme). It is a React app that runs inside your organization's Sanity Dashboard, in development and when deployed.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
- `npm run dev` starts the dev server and prints a Sanity Dashboard URL where your app runs. Open it and sign in with your Sanity account.
|
|
8
|
+
- `npm run build` builds the app for production.
|
|
9
|
+
- `npm run deploy` deploys the app to the Sanity Dashboard.
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
- `src/App.tsx` is the app entry point. The `SanityApp` config sets which project and dataset the app reads content from.
|
|
14
|
+
- `sanity.cli.ts` holds your organization ID and the app entry path.
|
|
15
|
+
|
|
16
|
+
## Learn more
|
|
17
|
+
|
|
18
|
+
- [App SDK Quickstart Guide](https://www.sanity.io/docs/app-sdk/sdk-quickstart?utm_source=readme)
|
|
19
|
+
- [App SDK documentation](https://www.sanity.io/docs/app-sdk?utm_source=readme)
|
|
20
|
+
- [Using Sanity UI with the App SDK](https://www.sanity.io/docs/app-sdk/sanity-ui-sdk?utm_source=readme)
|
|
21
|
+
- [API reference](https://reference.sanity.io/_sanity/sdk-react/)
|
|
22
|
+
- [Deploying your app](https://www.sanity.io/docs/app-sdk/sdk-deployment?utm_source=readme)
|
|
23
|
+
- [SDK Explorer with example apps](https://sdk-explorer.sanity.io)
|
|
@@ -18,13 +18,16 @@ export function ExampleComponent() {
|
|
|
18
18
|
</Text>
|
|
19
19
|
<Text muted>
|
|
20
20
|
You can also replace this component with components of your own. Render them in your
|
|
21
|
-
app by importing and using them in your application’s <code>src/App.tsx
|
|
22
|
-
|
|
21
|
+
app by importing and using them in your application’s <code>src/App.tsx</code> file. A
|
|
22
|
+
good next step is fetching content with the <code>useDocuments</code> hook. Data hooks
|
|
23
|
+
suspend while loading, so render them inside a <code>{'<Suspense>'}</code> boundary.
|
|
23
24
|
</Text>
|
|
24
25
|
<Text muted>
|
|
25
|
-
Looking for more guidance? See the
|
|
26
|
-
|
|
27
|
-
<a href="https://reference.sanity.io/_sanity/sdk-react/">
|
|
26
|
+
Looking for more guidance? See the{' '}
|
|
27
|
+
<a href="https://www.sanity.io/docs/app-sdk">App SDK documentation</a>, the{' '}
|
|
28
|
+
<a href="https://reference.sanity.io/_sanity/sdk-react/">API reference</a>, the{' '}
|
|
29
|
+
<a href="https://sanity.io/ui">Sanity UI docs</a>, and example apps in the{' '}
|
|
30
|
+
<a href="https://sdk-explorer.sanity.io">SDK Explorer</a>.
|
|
28
31
|
</Text>
|
|
29
32
|
</Stack>
|
|
30
33
|
</Flex>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { styleText } from 'node:util';
|
|
2
|
-
import { isInteractive } from '@sanity/cli-core';
|
|
3
|
-
import { select } from '@sanity/cli-core/ux';
|
|
4
|
-
/**
|
|
5
|
-
* Handle prerelease versions that cannot be resolved by the auto-updates CDN.
|
|
6
|
-
*
|
|
7
|
-
* In unattended or non-interactive mode, exits with an error. In interactive mode,
|
|
8
|
-
* prompts the user to either disable auto-updates for this build or cancel.
|
|
9
|
-
*
|
|
10
|
-
* Does not return if the build should be cancelled (exits via `output.error`).
|
|
11
|
-
*/ export async function handlePrereleaseVersions({ output, unattendedMode, unresolvedPrerelease }) {
|
|
12
|
-
const prereleaseMessage = `The following packages are using prerelease versions not yet available on the auto-updates CDN:\n\n` + `${unresolvedPrerelease.map((mod)=>` - ${mod.pkg} (${mod.version})`).join('\n')}\n\n` + `Auto-updates cannot be used with prerelease versions. To re-enable auto-updates later, ` + `switch to a non-prerelease version locally and deploy again.`;
|
|
13
|
-
if (unattendedMode || !isInteractive()) {
|
|
14
|
-
output.error(`${prereleaseMessage}\n\n` + `Cannot build with auto-updates in unattended mode when using prerelease versions. ` + `Either switch to a non-prerelease version, or use --no-auto-updates to build without auto-updates.`, {
|
|
15
|
-
exit: 1
|
|
16
|
-
});
|
|
17
|
-
// output.error with exit: 1 throws, but TypeScript doesn't know that
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
const choice = await select({
|
|
21
|
-
choices: [
|
|
22
|
-
{
|
|
23
|
-
name: 'Disable auto-updates for this build and continue',
|
|
24
|
-
value: 'disable-auto-updates'
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: 'Cancel build',
|
|
28
|
-
value: 'cancel'
|
|
29
|
-
}
|
|
30
|
-
],
|
|
31
|
-
default: 'disable-auto-updates',
|
|
32
|
-
message: styleText('yellow', prereleaseMessage)
|
|
33
|
-
});
|
|
34
|
-
if (choice === 'cancel') {
|
|
35
|
-
output.error('Declined to continue with build', {
|
|
36
|
-
exit: 1
|
|
37
|
-
});
|
|
38
|
-
// output.error with exit: 1 throws, but TypeScript doesn't know that
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
output.warn('Auto-updates disabled for this build');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
//# sourceMappingURL=handlePrereleaseVersions.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/build/handlePrereleaseVersions.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {isInteractive, type Output} from '@sanity/cli-core'\nimport {select} from '@sanity/cli-core/ux'\n\nimport {type UnresolvedPrerelease} from '../../util/compareDependencyVersions.js'\n\n/**\n * Handle prerelease versions that cannot be resolved by the auto-updates CDN.\n *\n * In unattended or non-interactive mode, exits with an error. In interactive mode,\n * prompts the user to either disable auto-updates for this build or cancel.\n *\n * Does not return if the build should be cancelled (exits via `output.error`).\n */\nexport async function handlePrereleaseVersions({\n output,\n unattendedMode,\n unresolvedPrerelease,\n}: {\n output: Output\n unattendedMode: boolean\n unresolvedPrerelease: UnresolvedPrerelease[]\n}): Promise<void> {\n const prereleaseMessage =\n `The following packages are using prerelease versions not yet available on the auto-updates CDN:\\n\\n` +\n `${unresolvedPrerelease.map((mod) => ` - ${mod.pkg} (${mod.version})`).join('\\n')}\\n\\n` +\n `Auto-updates cannot be used with prerelease versions. To re-enable auto-updates later, ` +\n `switch to a non-prerelease version locally and deploy again.`\n\n if (unattendedMode || !isInteractive()) {\n output.error(\n `${prereleaseMessage}\\n\\n` +\n `Cannot build with auto-updates in unattended mode when using prerelease versions. ` +\n `Either switch to a non-prerelease version, or use --no-auto-updates to build without auto-updates.`,\n {exit: 1},\n )\n // output.error with exit: 1 throws, but TypeScript doesn't know that\n return\n }\n\n const choice = await select({\n choices: [\n {\n name: 'Disable auto-updates for this build and continue',\n value: 'disable-auto-updates',\n },\n {name: 'Cancel build', value: 'cancel'},\n ],\n default: 'disable-auto-updates',\n message: styleText('yellow', prereleaseMessage),\n })\n\n if (choice === 'cancel') {\n output.error('Declined to continue with build', {exit: 1})\n // output.error with exit: 1 throws, but TypeScript doesn't know that\n return\n }\n\n output.warn('Auto-updates disabled for this build')\n}\n"],"names":["styleText","isInteractive","select","handlePrereleaseVersions","output","unattendedMode","unresolvedPrerelease","prereleaseMessage","map","mod","pkg","version","join","error","exit","choice","choices","name","value","default","message","warn"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,aAAa,QAAoB,mBAAkB;AAC3D,SAAQC,MAAM,QAAO,sBAAqB;AAI1C;;;;;;;CAOC,GACD,OAAO,eAAeC,yBAAyB,EAC7CC,MAAM,EACNC,cAAc,EACdC,oBAAoB,EAKrB;IACC,MAAMC,oBACJ,CAAC,mGAAmG,CAAC,GACrG,GAAGD,qBAAqBE,GAAG,CAAC,CAACC,MAAQ,CAAC,GAAG,EAAEA,IAAIC,GAAG,CAAC,EAAE,EAAED,IAAIE,OAAO,CAAC,CAAC,CAAC,EAAEC,IAAI,CAAC,MAAM,IAAI,CAAC,GACvF,CAAC,uFAAuF,CAAC,GACzF,CAAC,4DAA4D,CAAC;IAEhE,IAAIP,kBAAkB,CAACJ,iBAAiB;QACtCG,OAAOS,KAAK,CACV,GAAGN,kBAAkB,IAAI,CAAC,GACxB,CAAC,kFAAkF,CAAC,GACpF,CAAC,kGAAkG,CAAC,EACtG;YAACO,MAAM;QAAC;QAEV,qEAAqE;QACrE;IACF;IAEA,MAAMC,SAAS,MAAMb,OAAO;QAC1Bc,SAAS;YACP;gBACEC,MAAM;gBACNC,OAAO;YACT;YACA;gBAACD,MAAM;gBAAgBC,OAAO;YAAQ;SACvC;QACDC,SAAS;QACTC,SAASpB,UAAU,UAAUO;IAC/B;IAEA,IAAIQ,WAAW,UAAU;QACvBX,OAAOS,KAAK,CAAC,mCAAmC;YAACC,MAAM;QAAC;QACxD,qEAAqE;QACrE;IACF;IAEAV,OAAOiB,IAAI,CAAC;AACd"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/createStudioUserApplication.ts"],"sourcesContent":["import {CLIError} from '@oclif/core/errors'\nimport {input} from '@sanity/cli-core/ux'\n\nimport {createUserApplication, type UserApplication} from '../../services/userApplications.js'\nimport {deployDebug} from './deployDebug.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\n// TODO: replace with `Promise.withResolvers()` once it lands in node 22\nfunction promiseWithResolvers<T>() {\n let resolve!: (t: T) => void\n let reject!: (err: unknown) => void\n const promise = new Promise<T>((res, rej) => {\n resolve = res\n reject = rej\n })\n return {promise, reject, resolve}\n}\n\ninterface CreateStudioUserApplicationOptions {\n projectId: string\n\n urlType?: 'external' | 'internal'\n}\n\nexport async function createStudioUserApplication(options: CreateStudioUserApplicationOptions) {\n const {projectId, urlType = 'internal'} = options\n const {promise, resolve} = promiseWithResolvers<UserApplication>()\n\n const isExternal = urlType === 'external'\n\n await input({\n message: isExternal ? 'Studio URL (https://...):' : 'Studio hostname (<value>.sanity.studio):',\n // if a string is returned here, it is relayed to the user and prompt allows\n // the user to try again until this function returns true\n validate: async (inp: string) => {\n let appHost: string\n\n if (isExternal) {\n const normalized = normalizeUrl(inp)\n const validation = validateUrl(normalized)\n if (validation !== true) {\n return validation\n }\n appHost = normalized\n } else {\n appHost = inp.replace(/\\.sanity\\.studio$/i, '')\n }\n\n try {\n const response = await createUserApplication({\n appType: 'studio',\n body: {\n appHost,\n type: 'studio',\n urlType,\n },\n projectId,\n })\n resolve(response)\n return true\n } catch (e) {\n // if the name is taken, it should return a 409 so we relay to the user\n if ([402, 409].includes(e?.statusCode)) {\n return e?.response?.body?.message || 'Bad request' // just in case\n }\n\n deployDebug('Error creating user application', e)\n // otherwise, it's a fatal error\n throw new CLIError('Error creating user application', {exit: 1})\n }\n },\n })\n\n return await promise\n}\n"],"names":["CLIError","input","createUserApplication","deployDebug","normalizeUrl","validateUrl","promiseWithResolvers","resolve","reject","promise","Promise","res","rej","createStudioUserApplication","options","projectId","urlType","isExternal","message","validate","inp","appHost","normalized","validation","replace","response","appType","body","type","e","includes","statusCode","exit"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,qBAAoB;AAC3C,SAAQC,KAAK,QAAO,sBAAqB;AAEzC,SAAQC,qBAAqB,QAA6B,qCAAoC;AAC9F,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAQC,YAAY,EAAEC,WAAW,QAAO,gBAAe;AAEvD,wEAAwE;AACxE,SAASC;IACP,IAAIC;IACJ,IAAIC;IACJ,MAAMC,UAAU,IAAIC,QAAW,CAACC,KAAKC;QACnCL,UAAUI;QACVH,SAASI;IACX;IACA,OAAO;QAACH;QAASD;QAAQD;IAAO;AAClC;AAQA,OAAO,eAAeM,4BAA4BC,OAA2C;IAC3F,MAAM,EAACC,SAAS,EAAEC,UAAU,UAAU,EAAC,GAAGF;IAC1C,MAAM,EAACL,OAAO,EAAEF,OAAO,EAAC,GAAGD;IAE3B,MAAMW,aAAaD,YAAY;IAE/B,MAAMf,MAAM;QACViB,SAASD,aAAa,8BAA8B;QACpD,4EAA4E;QAC5E,yDAAyD;QACzDE,UAAU,OAAOC;YACf,IAAIC;YAEJ,IAAIJ,YAAY;gBACd,MAAMK,aAAalB,aAAagB;gBAChC,MAAMG,aAAalB,YAAYiB;gBAC/B,IAAIC,eAAe,MAAM;oBACvB,OAAOA;gBACT;gBACAF,UAAUC;YACZ,OAAO;gBACLD,UAAUD,IAAII,OAAO,CAAC,sBAAsB;YAC9C;YAEA,IAAI;gBACF,MAAMC,WAAW,MAAMvB,sBAAsB;oBAC3CwB,SAAS;oBACTC,MAAM;wBACJN;wBACAO,MAAM;wBACNZ;oBACF;oBACAD;gBACF;gBACAR,QAAQkB;gBACR,OAAO;YACT,EAAE,OAAOI,GAAG;gBACV,uEAAuE;gBACvE,IAAI;oBAAC;oBAAK;iBAAI,CAACC,QAAQ,CAACD,GAAGE,aAAa;oBACtC,OAAOF,GAAGJ,UAAUE,MAAMT,WAAW,cAAc,eAAe;;gBACpE;gBAEAf,YAAY,mCAAmC0B;gBAC/C,gCAAgC;gBAChC,MAAM,IAAI7B,SAAS,mCAAmC;oBAACgC,MAAM;gBAAC;YAChE;QACF;IACF;IAEA,OAAO,MAAMvB;AACf"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { input, spinner } from '@sanity/cli-core/ux';
|
|
2
|
-
import { customAlphabet } from 'nanoid';
|
|
3
|
-
import { createUserApplication } from '../../services/userApplications.js';
|
|
4
|
-
import { NO_ORGANIZATION_ID } from '../../util/errorMessages.js';
|
|
5
|
-
import { deployDebug } from './deployDebug.js';
|
|
6
|
-
export async function createUserApplicationForApp(organizationId) {
|
|
7
|
-
if (!organizationId) {
|
|
8
|
-
throw new Error(NO_ORGANIZATION_ID);
|
|
9
|
-
}
|
|
10
|
-
// First get the title from the user
|
|
11
|
-
const title = await input({
|
|
12
|
-
message: 'Enter a title for your application:',
|
|
13
|
-
validate: (input)=>input.length > 0 || 'Title is required'
|
|
14
|
-
});
|
|
15
|
-
return tryCreateApp(title, organizationId);
|
|
16
|
-
}
|
|
17
|
-
// appHosts have some restrictions (no uppercase, must start with a letter)
|
|
18
|
-
const generateId = ()=>{
|
|
19
|
-
const letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
20
|
-
const firstChar = customAlphabet(letters, 1)();
|
|
21
|
-
const rest = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 11)();
|
|
22
|
-
return `${firstChar}${rest}`;
|
|
23
|
-
};
|
|
24
|
-
const tryCreateApp = async (title, organizationId)=>{
|
|
25
|
-
// we will likely prepend this with an org ID or other parameter in the future
|
|
26
|
-
const appHost = generateId();
|
|
27
|
-
const spin = spinner('Creating application').start();
|
|
28
|
-
try {
|
|
29
|
-
const response = await createUserApplication({
|
|
30
|
-
appType: 'coreApp',
|
|
31
|
-
body: {
|
|
32
|
-
appHost,
|
|
33
|
-
title,
|
|
34
|
-
type: 'coreApp',
|
|
35
|
-
urlType: 'internal'
|
|
36
|
-
},
|
|
37
|
-
organizationId
|
|
38
|
-
});
|
|
39
|
-
spin.succeed();
|
|
40
|
-
return response;
|
|
41
|
-
} catch (e) {
|
|
42
|
-
// if the name is taken, generate a new one and try again
|
|
43
|
-
if ([
|
|
44
|
-
402,
|
|
45
|
-
409
|
|
46
|
-
].includes(e?.statusCode)) {
|
|
47
|
-
deployDebug('App host taken, retrying with new host');
|
|
48
|
-
return tryCreateApp(title, organizationId);
|
|
49
|
-
}
|
|
50
|
-
spin.fail();
|
|
51
|
-
deployDebug('Error creating core application', e);
|
|
52
|
-
throw e;
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
//# sourceMappingURL=createUserApplicationForApp.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/createUserApplicationForApp.ts"],"sourcesContent":["import {input, spinner} from '@sanity/cli-core/ux'\nimport {customAlphabet} from 'nanoid'\n\nimport {createUserApplication, type UserApplication} from '../../services/userApplications.js'\nimport {NO_ORGANIZATION_ID} from '../../util/errorMessages.js'\nimport {deployDebug} from './deployDebug.js'\n\nexport async function createUserApplicationForApp(\n organizationId?: string,\n): Promise<UserApplication> {\n if (!organizationId) {\n throw new Error(NO_ORGANIZATION_ID)\n }\n\n // First get the title from the user\n const title = await input({\n message: 'Enter a title for your application:',\n validate: (input: string) => input.length > 0 || 'Title is required',\n })\n\n return tryCreateApp(title, organizationId)\n}\n\n// appHosts have some restrictions (no uppercase, must start with a letter)\nconst generateId = () => {\n const letters = 'abcdefghijklmnopqrstuvwxyz'\n const firstChar = customAlphabet(letters, 1)()\n const rest = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 11)()\n return `${firstChar}${rest}`\n}\n\nconst tryCreateApp = async (title: string, organizationId: string) => {\n // we will likely prepend this with an org ID or other parameter in the future\n const appHost = generateId()\n\n const spin = spinner('Creating application').start()\n\n try {\n const response = await createUserApplication({\n appType: 'coreApp',\n body: {\n appHost,\n title,\n type: 'coreApp',\n urlType: 'internal',\n },\n organizationId,\n })\n\n spin.succeed()\n return response\n } catch (e) {\n // if the name is taken, generate a new one and try again\n if ([402, 409].includes(e?.statusCode)) {\n deployDebug('App host taken, retrying with new host')\n return tryCreateApp(title, organizationId)\n }\n\n spin.fail()\n\n deployDebug('Error creating core application', e)\n throw e\n }\n}\n"],"names":["input","spinner","customAlphabet","createUserApplication","NO_ORGANIZATION_ID","deployDebug","createUserApplicationForApp","organizationId","Error","title","message","validate","length","tryCreateApp","generateId","letters","firstChar","rest","appHost","spin","start","response","appType","body","type","urlType","succeed","e","includes","statusCode","fail"],"mappings":"AAAA,SAAQA,KAAK,EAAEC,OAAO,QAAO,sBAAqB;AAClD,SAAQC,cAAc,QAAO,SAAQ;AAErC,SAAQC,qBAAqB,QAA6B,qCAAoC;AAC9F,SAAQC,kBAAkB,QAAO,8BAA6B;AAC9D,SAAQC,WAAW,QAAO,mBAAkB;AAE5C,OAAO,eAAeC,4BACpBC,cAAuB;IAEvB,IAAI,CAACA,gBAAgB;QACnB,MAAM,IAAIC,MAAMJ;IAClB;IAEA,oCAAoC;IACpC,MAAMK,QAAQ,MAAMT,MAAM;QACxBU,SAAS;QACTC,UAAU,CAACX,QAAkBA,MAAMY,MAAM,GAAG,KAAK;IACnD;IAEA,OAAOC,aAAaJ,OAAOF;AAC7B;AAEA,2EAA2E;AAC3E,MAAMO,aAAa;IACjB,MAAMC,UAAU;IAChB,MAAMC,YAAYd,eAAea,SAAS;IAC1C,MAAME,OAAOf,eAAe,wCAAwC;IACpE,OAAO,GAAGc,YAAYC,MAAM;AAC9B;AAEA,MAAMJ,eAAe,OAAOJ,OAAeF;IACzC,8EAA8E;IAC9E,MAAMW,UAAUJ;IAEhB,MAAMK,OAAOlB,QAAQ,wBAAwBmB,KAAK;IAElD,IAAI;QACF,MAAMC,WAAW,MAAMlB,sBAAsB;YAC3CmB,SAAS;YACTC,MAAM;gBACJL;gBACAT;gBACAe,MAAM;gBACNC,SAAS;YACX;YACAlB;QACF;QAEAY,KAAKO,OAAO;QACZ,OAAOL;IACT,EAAE,OAAOM,GAAG;QACV,yDAAyD;QACzD,IAAI;YAAC;YAAK;SAAI,CAACC,QAAQ,CAACD,GAAGE,aAAa;YACtCxB,YAAY;YACZ,OAAOQ,aAAaJ,OAAOF;QAC7B;QAEAY,KAAKW,IAAI;QAETzB,YAAY,mCAAmCsB;QAC/C,MAAMA;IACR;AACF"}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Helper functions to find a user application for a Sanity application.
|
|
3
|
-
*/ import { select, Separator, spinner } from '@sanity/cli-core/ux';
|
|
4
|
-
import { getUserApplication, getUserApplications } from '../../services/userApplications.js';
|
|
5
|
-
import { checkForDeprecatedAppId, getAppId } from '../../util/appId.js';
|
|
6
|
-
import { deployDebug } from './deployDebug.js';
|
|
7
|
-
/**
|
|
8
|
-
* Find a user application for a Sanity application.
|
|
9
|
-
*/ export async function findUserApplicationForApp(options) {
|
|
10
|
-
const { cliConfig, organizationId, output } = options;
|
|
11
|
-
const spin = spinner('Checking application info...').start();
|
|
12
|
-
try {
|
|
13
|
-
checkForDeprecatedAppId({
|
|
14
|
-
cliConfig,
|
|
15
|
-
output
|
|
16
|
-
});
|
|
17
|
-
deployDebug('Checking for a user application as specified in the local app config');
|
|
18
|
-
const userApplication = await findUserApplication(options);
|
|
19
|
-
if (userApplication) {
|
|
20
|
-
deployDebug('Found a user application as configured');
|
|
21
|
-
spin.succeed();
|
|
22
|
-
return userApplication;
|
|
23
|
-
}
|
|
24
|
-
const appId = getAppId(cliConfig);
|
|
25
|
-
// If there's an appId in the application config but there's no userApplication,
|
|
26
|
-
// then the provided application ID doesn’t exist in the org
|
|
27
|
-
if (appId) {
|
|
28
|
-
spin.clear();
|
|
29
|
-
output.error('The `appId` provided in your configuration’s `deployment` object cannot be found in your organization', {
|
|
30
|
-
exit: 1,
|
|
31
|
-
suggestions: [
|
|
32
|
-
'Verify the appId in your configuration matches an existing application'
|
|
33
|
-
]
|
|
34
|
-
});
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
// Done checking local application info
|
|
38
|
-
// Update the spinner text to indicate the next operation
|
|
39
|
-
spin.text = 'No application ID configured; checking for existing applications...';
|
|
40
|
-
// Get a list of existing applications to select from.
|
|
41
|
-
// This will fail if the org ID is malformed or the user doesn’t have access to the org ID
|
|
42
|
-
const userApplications = await getUserApplications({
|
|
43
|
-
appType: 'coreApp',
|
|
44
|
-
organizationId
|
|
45
|
-
});
|
|
46
|
-
// If no applications are found, return null
|
|
47
|
-
if (!userApplications?.length) {
|
|
48
|
-
spin.info('No application ID configured');
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
// No app ID configured, done checking for existing applications;
|
|
52
|
-
// retain this spinner text for clarity
|
|
53
|
-
spin.info('No application ID configured');
|
|
54
|
-
// Enumerate the available applications
|
|
55
|
-
const choices = userApplications.map((app)=>({
|
|
56
|
-
name: app.title ?? app.appHost,
|
|
57
|
-
value: app.appHost
|
|
58
|
-
}));
|
|
59
|
-
// Ask the user to select an existing app or create a new one
|
|
60
|
-
const selected = await select({
|
|
61
|
-
choices: [
|
|
62
|
-
{
|
|
63
|
-
name: 'New application deployment',
|
|
64
|
-
value: 'NEW_APP'
|
|
65
|
-
},
|
|
66
|
-
new Separator(' ════ Existing applications: ════ '),
|
|
67
|
-
...choices
|
|
68
|
-
],
|
|
69
|
-
loop: false,
|
|
70
|
-
message: 'Would you like to create a new application deployment, or deploy to an existing one?',
|
|
71
|
-
pageSize: 10
|
|
72
|
-
});
|
|
73
|
-
// If the user wants to create a new deployed application, return null
|
|
74
|
-
if (selected === 'NEW_APP') {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
return userApplications.find((app)=>app.appHost === selected);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
// User can't access applications for the org
|
|
80
|
-
if (error?.statusCode === 403) {
|
|
81
|
-
spin.clear();
|
|
82
|
-
deployDebug('User does not have permission to get applications for the org, or the org ID is malformed/doesn’t exist', error);
|
|
83
|
-
output.error(`You don’t have permission to view applications for the configured organization ID ("${organizationId}")`, {
|
|
84
|
-
exit: 1,
|
|
85
|
-
suggestions: [
|
|
86
|
-
'Verify that you’ve entered the correct organization ID',
|
|
87
|
-
'Ask your Sanity organization’s admin to provide you with the proper permissions'
|
|
88
|
-
]
|
|
89
|
-
});
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
// We've failed for some other reason
|
|
93
|
-
spin.clear();
|
|
94
|
-
deployDebug('Error finding user application for app', error);
|
|
95
|
-
output.error(error);
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function findUserApplication(options) {
|
|
100
|
-
const { cliConfig } = options;
|
|
101
|
-
const appId = getAppId(cliConfig);
|
|
102
|
-
if (!appId) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
return getUserApplication({
|
|
106
|
-
appId,
|
|
107
|
-
isSdkApp: true
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
//# sourceMappingURL=findUserApplicationForApp.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/findUserApplicationForApp.ts"],"sourcesContent":["/**\n * Helper functions to find a user application for a Sanity application.\n */\n\nimport {type CliConfig, type Output} from '@sanity/cli-core'\nimport {select, Separator, spinner} from '@sanity/cli-core/ux'\n\nimport {\n getUserApplication,\n getUserApplications,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {checkForDeprecatedAppId, getAppId} from '../../util/appId.js'\nimport {deployDebug} from './deployDebug.js'\n\ninterface FindUserApplicationForAppOptions {\n cliConfig: CliConfig\n organizationId: string\n output: Output\n}\n\n/**\n * Find a user application for a Sanity application.\n */\nexport async function findUserApplicationForApp(\n options: FindUserApplicationForAppOptions,\n): Promise<UserApplication | null> {\n const {cliConfig, organizationId, output} = options\n\n const spin = spinner('Checking application info...').start()\n\n try {\n checkForDeprecatedAppId({cliConfig, output})\n\n deployDebug('Checking for a user application as specified in the local app config')\n const userApplication = await findUserApplication(options)\n\n if (userApplication) {\n deployDebug('Found a user application as configured')\n spin.succeed()\n return userApplication\n }\n\n const appId = getAppId(cliConfig)\n\n // If there's an appId in the application config but there's no userApplication,\n // then the provided application ID doesn’t exist in the org\n if (appId) {\n spin.clear()\n output.error(\n 'The `appId` provided in your configuration’s `deployment` object cannot be found in your organization',\n {\n exit: 1,\n suggestions: ['Verify the appId in your configuration matches an existing application'],\n },\n )\n return null\n }\n\n // Done checking local application info\n // Update the spinner text to indicate the next operation\n spin.text = 'No application ID configured; checking for existing applications...'\n\n // Get a list of existing applications to select from.\n // This will fail if the org ID is malformed or the user doesn’t have access to the org ID\n const userApplications = await getUserApplications({\n appType: 'coreApp',\n organizationId,\n })\n\n // If no applications are found, return null\n if (!userApplications?.length) {\n spin.info('No application ID configured')\n return null\n }\n\n // No app ID configured, done checking for existing applications;\n // retain this spinner text for clarity\n spin.info('No application ID configured')\n\n // Enumerate the available applications\n const choices = userApplications.map((app) => ({\n name: app.title ?? app.appHost,\n value: app.appHost,\n }))\n\n // Ask the user to select an existing app or create a new one\n const selected = await select({\n choices: [\n {name: 'New application deployment', value: 'NEW_APP'},\n new Separator(' ════ Existing applications: ════ '),\n ...choices,\n ],\n loop: false,\n message:\n 'Would you like to create a new application deployment, or deploy to an existing one?',\n pageSize: 10,\n })\n\n // If the user wants to create a new deployed application, return null\n if (selected === 'NEW_APP') {\n return null\n }\n\n return userApplications.find((app) => app.appHost === selected)!\n } catch (error) {\n // User can't access applications for the org\n if (error?.statusCode === 403) {\n spin.clear()\n deployDebug(\n 'User does not have permission to get applications for the org, or the org ID is malformed/doesn’t exist',\n error,\n )\n output.error(\n `You don’t have permission to view applications for the configured organization ID (\"${organizationId}\")`,\n {\n exit: 1,\n suggestions: [\n 'Verify that you’ve entered the correct organization ID',\n 'Ask your Sanity organization’s admin to provide you with the proper permissions',\n ],\n },\n )\n return null\n }\n\n // We've failed for some other reason\n spin.clear()\n deployDebug('Error finding user application for app', error)\n output.error(error)\n return null\n }\n}\n\nfunction findUserApplication(\n options: FindUserApplicationForAppOptions,\n): Promise<UserApplication | null> | null {\n const {cliConfig} = options\n\n const appId = getAppId(cliConfig)\n\n if (!appId) {\n return null\n }\n\n return getUserApplication({appId, isSdkApp: true})\n}\n"],"names":["select","Separator","spinner","getUserApplication","getUserApplications","checkForDeprecatedAppId","getAppId","deployDebug","findUserApplicationForApp","options","cliConfig","organizationId","output","spin","start","userApplication","findUserApplication","succeed","appId","clear","error","exit","suggestions","text","userApplications","appType","length","info","choices","map","app","name","title","appHost","value","selected","loop","message","pageSize","find","statusCode","isSdkApp"],"mappings":"AAAA;;CAEC,GAGD,SAAQA,MAAM,EAAEC,SAAS,EAAEC,OAAO,QAAO,sBAAqB;AAE9D,SACEC,kBAAkB,EAClBC,mBAAmB,QAEd,qCAAoC;AAC3C,SAAQC,uBAAuB,EAAEC,QAAQ,QAAO,sBAAqB;AACrE,SAAQC,WAAW,QAAO,mBAAkB;AAQ5C;;CAEC,GACD,OAAO,eAAeC,0BACpBC,OAAyC;IAEzC,MAAM,EAACC,SAAS,EAAEC,cAAc,EAAEC,MAAM,EAAC,GAAGH;IAE5C,MAAMI,OAAOX,QAAQ,gCAAgCY,KAAK;IAE1D,IAAI;QACFT,wBAAwB;YAACK;YAAWE;QAAM;QAE1CL,YAAY;QACZ,MAAMQ,kBAAkB,MAAMC,oBAAoBP;QAElD,IAAIM,iBAAiB;YACnBR,YAAY;YACZM,KAAKI,OAAO;YACZ,OAAOF;QACT;QAEA,MAAMG,QAAQZ,SAASI;QAEvB,gFAAgF;QAChF,4DAA4D;QAC5D,IAAIQ,OAAO;YACTL,KAAKM,KAAK;YACVP,OAAOQ,KAAK,CACV,yGACA;gBACEC,MAAM;gBACNC,aAAa;oBAAC;iBAAyE;YACzF;YAEF,OAAO;QACT;QAEA,uCAAuC;QACvC,yDAAyD;QACzDT,KAAKU,IAAI,GAAG;QAEZ,sDAAsD;QACtD,0FAA0F;QAC1F,MAAMC,mBAAmB,MAAMpB,oBAAoB;YACjDqB,SAAS;YACTd;QACF;QAEA,4CAA4C;QAC5C,IAAI,CAACa,kBAAkBE,QAAQ;YAC7Bb,KAAKc,IAAI,CAAC;YACV,OAAO;QACT;QAEA,iEAAiE;QACjE,uCAAuC;QACvCd,KAAKc,IAAI,CAAC;QAEV,uCAAuC;QACvC,MAAMC,UAAUJ,iBAAiBK,GAAG,CAAC,CAACC,MAAS,CAAA;gBAC7CC,MAAMD,IAAIE,KAAK,IAAIF,IAAIG,OAAO;gBAC9BC,OAAOJ,IAAIG,OAAO;YACpB,CAAA;QAEA,6DAA6D;QAC7D,MAAME,WAAW,MAAMnC,OAAO;YAC5B4B,SAAS;gBACP;oBAACG,MAAM;oBAA8BG,OAAO;gBAAS;gBACrD,IAAIjC,UAAU;mBACX2B;aACJ;YACDQ,MAAM;YACNC,SACE;YACFC,UAAU;QACZ;QAEA,sEAAsE;QACtE,IAAIH,aAAa,WAAW;YAC1B,OAAO;QACT;QAEA,OAAOX,iBAAiBe,IAAI,CAAC,CAACT,MAAQA,IAAIG,OAAO,KAAKE;IACxD,EAAE,OAAOf,OAAO;QACd,6CAA6C;QAC7C,IAAIA,OAAOoB,eAAe,KAAK;YAC7B3B,KAAKM,KAAK;YACVZ,YACE,2GACAa;YAEFR,OAAOQ,KAAK,CACV,CAAC,oFAAoF,EAAET,eAAe,EAAE,CAAC,EACzG;gBACEU,MAAM;gBACNC,aAAa;oBACX;oBACA;iBACD;YACH;YAEF,OAAO;QACT;QAEA,qCAAqC;QACrCT,KAAKM,KAAK;QACVZ,YAAY,0CAA0Ca;QACtDR,OAAOQ,KAAK,CAACA;QACb,OAAO;IACT;AACF;AAEA,SAASJ,oBACPP,OAAyC;IAEzC,MAAM,EAACC,SAAS,EAAC,GAAGD;IAEpB,MAAMS,QAAQZ,SAASI;IAEvB,IAAI,CAACQ,OAAO;QACV,OAAO;IACT;IAEA,OAAOf,mBAAmB;QAACe;QAAOuB,UAAU;IAAI;AAClD"}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Helper functions to find a user application for a Sanity studio.
|
|
3
|
-
*/ import { exitCodes } from '@sanity/cli-core';
|
|
4
|
-
import { select, Separator, spinner } from '@sanity/cli-core/ux';
|
|
5
|
-
import { createUserApplication, getUserApplication, getUserApplications } from '../../services/userApplications.js';
|
|
6
|
-
import { deployDebug } from './deployDebug.js';
|
|
7
|
-
import { normalizeUrl, validateUrl } from './urlUtils.js';
|
|
8
|
-
export async function findUserApplicationForStudio(options) {
|
|
9
|
-
const { appHost, appId, output, projectId, unattended = false, urlType = 'internal' } = options;
|
|
10
|
-
const spin = spinner('Checking project info').start();
|
|
11
|
-
const userApplication = await findUserApplication({
|
|
12
|
-
appHost,
|
|
13
|
-
appId,
|
|
14
|
-
output,
|
|
15
|
-
projectId,
|
|
16
|
-
spin,
|
|
17
|
-
urlType
|
|
18
|
-
});
|
|
19
|
-
spin.succeed();
|
|
20
|
-
if (userApplication) {
|
|
21
|
-
return userApplication;
|
|
22
|
-
}
|
|
23
|
-
// No user application found, so let's list out the existing user applications
|
|
24
|
-
// along with an option to create a new one
|
|
25
|
-
let userApplications = [];
|
|
26
|
-
// Get existing user applications (if any),
|
|
27
|
-
// based on the configured project ID
|
|
28
|
-
if (projectId) {
|
|
29
|
-
const allApps = await getUserApplications({
|
|
30
|
-
appType: 'studio',
|
|
31
|
-
projectId
|
|
32
|
-
});
|
|
33
|
-
// Filter by urlType so external deploys only see external studios and vice versa
|
|
34
|
-
userApplications = allApps?.filter((app)=>app.urlType === urlType) ?? [];
|
|
35
|
-
}
|
|
36
|
-
// If no applications are found, return null
|
|
37
|
-
if (!userApplications?.length) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
// In unattended mode, we can't prompt the user to select a studio.
|
|
41
|
-
// Return null and let the caller handle the error messaging.
|
|
42
|
-
if (unattended) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
// If there are user applications, allow the user to select one of the existing host names,
|
|
46
|
-
// or to create a new one
|
|
47
|
-
const newLabel = urlType === 'external' ? 'Register new external studio URL' : 'Create new studio hostname';
|
|
48
|
-
const selectMessage = urlType === 'external' ? 'Select existing external studio, or register a new one' : 'Select existing studio hostname, or create a new one';
|
|
49
|
-
const choices = userApplications.map((app)=>({
|
|
50
|
-
name: app.title ?? app.appHost,
|
|
51
|
-
value: app.appHost
|
|
52
|
-
}));
|
|
53
|
-
const selected = await select({
|
|
54
|
-
choices: [
|
|
55
|
-
{
|
|
56
|
-
name: newLabel,
|
|
57
|
-
value: 'NEW_STUDIO'
|
|
58
|
-
},
|
|
59
|
-
new Separator(),
|
|
60
|
-
...choices
|
|
61
|
-
],
|
|
62
|
-
message: selectMessage
|
|
63
|
-
});
|
|
64
|
-
// If the user wants to create a new deployed application, return null
|
|
65
|
-
if (selected === 'NEW_STUDIO') {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
// Otherwise, return the selected user application
|
|
69
|
-
return userApplications.find((app)=>app.appHost === selected);
|
|
70
|
-
}
|
|
71
|
-
async function findUserApplication(options) {
|
|
72
|
-
const { appHost, appId, output, projectId, urlType } = options;
|
|
73
|
-
let { spin } = options;
|
|
74
|
-
let userApplication;
|
|
75
|
-
// If the config has an appId, check for apps with that ID
|
|
76
|
-
if (appId) {
|
|
77
|
-
try {
|
|
78
|
-
userApplication = await getUserApplication({
|
|
79
|
-
appId,
|
|
80
|
-
isSdkApp: false,
|
|
81
|
-
projectId
|
|
82
|
-
});
|
|
83
|
-
if (userApplication) {
|
|
84
|
-
return userApplication;
|
|
85
|
-
}
|
|
86
|
-
// If appID is specified but no app is found with it, throw an error
|
|
87
|
-
throw new Error(`Cannot find app with app ID ${appId}`);
|
|
88
|
-
} catch (error) {
|
|
89
|
-
spin.fail();
|
|
90
|
-
deployDebug('Error finding user application', error);
|
|
91
|
-
output.error(`Error finding user application: ${error?.message}`, {
|
|
92
|
-
exit: 1
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// As a fallback, if studioHost (deprecated) is configured, check for apps with that host
|
|
97
|
-
if (appHost) {
|
|
98
|
-
// For external URLs, validate and normalize before lookup/creation
|
|
99
|
-
const resolvedHost = urlType === 'external' ? normalizeUrl(appHost) : appHost;
|
|
100
|
-
if (urlType === 'external') {
|
|
101
|
-
const validation = validateUrl(resolvedHost);
|
|
102
|
-
if (validation !== true) {
|
|
103
|
-
spin.fail();
|
|
104
|
-
output.error(validation, {
|
|
105
|
-
exit: exitCodes.USAGE_ERROR
|
|
106
|
-
});
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
userApplication = await getUserApplication({
|
|
112
|
-
appHost: resolvedHost,
|
|
113
|
-
isSdkApp: false,
|
|
114
|
-
projectId
|
|
115
|
-
});
|
|
116
|
-
// We've found the application — return it
|
|
117
|
-
if (userApplication) {
|
|
118
|
-
return userApplication;
|
|
119
|
-
}
|
|
120
|
-
// Otherwise, try to create an app with the configured host
|
|
121
|
-
try {
|
|
122
|
-
if (urlType === 'external') {
|
|
123
|
-
output.log('Your project has not been registered with an external studio URL.');
|
|
124
|
-
output.log(`Registering ${resolvedHost}`);
|
|
125
|
-
} else {
|
|
126
|
-
output.log('Your project has not been assigned a studio hostname.');
|
|
127
|
-
output.log(`Creating https://${resolvedHost}.sanity.studio`);
|
|
128
|
-
}
|
|
129
|
-
output.log('');
|
|
130
|
-
spin = spinner(urlType === 'external' ? 'Registering external studio' : 'Creating studio hostname').start();
|
|
131
|
-
const response = await createUserApplication({
|
|
132
|
-
appType: 'studio',
|
|
133
|
-
body: {
|
|
134
|
-
appHost: resolvedHost,
|
|
135
|
-
type: 'studio',
|
|
136
|
-
urlType
|
|
137
|
-
},
|
|
138
|
-
projectId
|
|
139
|
-
});
|
|
140
|
-
spin.succeed();
|
|
141
|
-
return response;
|
|
142
|
-
} catch (e) {
|
|
143
|
-
spin.fail();
|
|
144
|
-
// if the name is taken, it should return a 409 so we relay to the user
|
|
145
|
-
if ([
|
|
146
|
-
402,
|
|
147
|
-
409
|
|
148
|
-
].includes(e?.statusCode)) {
|
|
149
|
-
output.error(e?.response?.body?.message || 'Bad request', {
|
|
150
|
-
exit: 1
|
|
151
|
-
});
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
// otherwise, it's a fatal error
|
|
155
|
-
deployDebug('Error creating user application from config', e);
|
|
156
|
-
output.error(`Error creating user application from config: ${e instanceof Error ? e.message : e}`, {
|
|
157
|
-
exit: 1
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
} catch (error) {
|
|
161
|
-
spin.fail();
|
|
162
|
-
deployDebug('Error finding user application', error);
|
|
163
|
-
output.error(`Error finding user application: ${error instanceof Error ? error.message : error.toString()}`, {
|
|
164
|
-
exit: 1
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
// If no appID and no appHost, just return and proceed to check for studios with the project ID
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
//# sourceMappingURL=findUserApplicationForStudio.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/findUserApplicationForStudio.ts"],"sourcesContent":["/**\n * Helper functions to find a user application for a Sanity studio.\n */\n\nimport {exitCodes, type Output} from '@sanity/cli-core'\nimport {select, Separator, spinner, type SpinnerInstance} from '@sanity/cli-core/ux'\n\nimport {\n createUserApplication,\n getUserApplication,\n getUserApplications,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {deployDebug} from './deployDebug.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\ninterface FindUserApplicationForStudioOptions {\n output: Output\n projectId: string\n\n appHost?: string\n appId?: string\n unattended?: boolean\n urlType?: 'external' | 'internal'\n}\n\nexport async function findUserApplicationForStudio(options: FindUserApplicationForStudioOptions) {\n const {appHost, appId, output, projectId, unattended = false, urlType = 'internal'} = options\n\n const spin = spinner('Checking project info').start()\n\n const userApplication = await findUserApplication({\n appHost,\n appId,\n output,\n projectId,\n spin,\n urlType,\n })\n\n spin.succeed()\n\n if (userApplication) {\n return userApplication\n }\n\n // No user application found, so let's list out the existing user applications\n // along with an option to create a new one\n let userApplications: Array<UserApplication> = []\n\n // Get existing user applications (if any),\n // based on the configured project ID\n if (projectId) {\n const allApps = await getUserApplications({\n appType: 'studio',\n projectId,\n })\n // Filter by urlType so external deploys only see external studios and vice versa\n userApplications = allApps?.filter((app) => app.urlType === urlType) ?? []\n }\n\n // If no applications are found, return null\n if (!userApplications?.length) {\n return null\n }\n\n // In unattended mode, we can't prompt the user to select a studio.\n // Return null and let the caller handle the error messaging.\n if (unattended) {\n return null\n }\n\n // If there are user applications, allow the user to select one of the existing host names,\n // or to create a new one\n const newLabel =\n urlType === 'external' ? 'Register new external studio URL' : 'Create new studio hostname'\n const selectMessage =\n urlType === 'external'\n ? 'Select existing external studio, or register a new one'\n : 'Select existing studio hostname, or create a new one'\n\n const choices = userApplications.map((app) => ({\n name: app.title ?? app.appHost,\n value: app.appHost,\n }))\n\n const selected = await select({\n choices: [{name: newLabel, value: 'NEW_STUDIO'}, new Separator(), ...choices],\n message: selectMessage,\n })\n\n // If the user wants to create a new deployed application, return null\n if (selected === 'NEW_STUDIO') {\n return null\n }\n\n // Otherwise, return the selected user application\n return userApplications.find((app) => app.appHost === selected)!\n}\n\ninterface FindUserApplicationFromConfigOptions {\n output: Output\n projectId: string\n spin: SpinnerInstance\n urlType: 'external' | 'internal'\n\n appHost?: string\n appId?: string\n}\n\nasync function findUserApplication(\n options: FindUserApplicationFromConfigOptions,\n): Promise<UserApplication | null> {\n const {appHost, appId, output, projectId, urlType} = options\n let {spin} = options\n\n let userApplication: UserApplication | null\n\n // If the config has an appId, check for apps with that ID\n if (appId) {\n try {\n userApplication = await getUserApplication({appId, isSdkApp: false, projectId})\n\n if (userApplication) {\n return userApplication\n }\n\n // If appID is specified but no app is found with it, throw an error\n throw new Error(`Cannot find app with app ID ${appId}`)\n } catch (error) {\n spin.fail()\n deployDebug('Error finding user application', error)\n output.error(`Error finding user application: ${error?.message}`, {exit: 1})\n }\n }\n\n // As a fallback, if studioHost (deprecated) is configured, check for apps with that host\n if (appHost) {\n // For external URLs, validate and normalize before lookup/creation\n const resolvedHost = urlType === 'external' ? normalizeUrl(appHost) : appHost\n\n if (urlType === 'external') {\n const validation = validateUrl(resolvedHost)\n if (validation !== true) {\n spin.fail()\n output.error(validation, {exit: exitCodes.USAGE_ERROR})\n return null\n }\n }\n\n try {\n userApplication = await getUserApplication({\n appHost: resolvedHost,\n isSdkApp: false,\n projectId,\n })\n\n // We've found the application — return it\n if (userApplication) {\n return userApplication\n }\n\n // Otherwise, try to create an app with the configured host\n try {\n if (urlType === 'external') {\n output.log('Your project has not been registered with an external studio URL.')\n output.log(`Registering ${resolvedHost}`)\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log(`Creating https://${resolvedHost}.sanity.studio`)\n }\n output.log('')\n spin = spinner(\n urlType === 'external' ? 'Registering external studio' : 'Creating studio hostname',\n ).start()\n\n const response = await createUserApplication({\n appType: 'studio',\n body: {\n appHost: resolvedHost,\n type: 'studio',\n urlType,\n },\n projectId,\n })\n spin.succeed()\n\n return response\n } catch (e) {\n spin.fail()\n // if the name is taken, it should return a 409 so we relay to the user\n if ([402, 409].includes(e?.statusCode)) {\n output.error(e?.response?.body?.message || 'Bad request', {exit: 1})\n return null\n }\n // otherwise, it's a fatal error\n deployDebug('Error creating user application from config', e)\n output.error(\n `Error creating user application from config: ${e instanceof Error ? e.message : e}`,\n {exit: 1},\n )\n }\n } catch (error) {\n spin.fail()\n deployDebug('Error finding user application', error)\n output.error(\n `Error finding user application: ${error instanceof Error ? error.message : error.toString()}`,\n {exit: 1},\n )\n }\n }\n\n // If no appID and no appHost, just return and proceed to check for studios with the project ID\n return null\n}\n"],"names":["exitCodes","select","Separator","spinner","createUserApplication","getUserApplication","getUserApplications","deployDebug","normalizeUrl","validateUrl","findUserApplicationForStudio","options","appHost","appId","output","projectId","unattended","urlType","spin","start","userApplication","findUserApplication","succeed","userApplications","allApps","appType","filter","app","length","newLabel","selectMessage","choices","map","name","title","value","selected","message","find","isSdkApp","Error","error","fail","exit","resolvedHost","validation","USAGE_ERROR","log","response","body","type","e","includes","statusCode","toString"],"mappings":"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"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod/mini';
|
|
2
|
-
/**
|
|
3
|
-
* A view record as persisted to the application service: `type`, `name`, `src`,
|
|
4
|
-
* plus any view-type-specific attributes (passed through for storage).
|
|
5
|
-
*/ const viewRecordSchema = z.looseObject({
|
|
6
|
-
name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),
|
|
7
|
-
src: z.string(),
|
|
8
|
-
type: z.enum([
|
|
9
|
-
'panel'
|
|
10
|
-
])
|
|
11
|
-
});
|
|
12
|
-
/**
|
|
13
|
-
* Payload registering an app's views with the application service on deploy.
|
|
14
|
-
*
|
|
15
|
-
* Phase 1 stub: the service that stores views does not exist yet, so the
|
|
16
|
-
* payload is validated and logged only — never sent. Builds the contract the
|
|
17
|
-
* application-service endpoint will accept.
|
|
18
|
-
*/ const viewDeploymentPayloadSchema = z.object({
|
|
19
|
-
applicationId: z.string(),
|
|
20
|
-
views: z.array(viewRecordSchema)
|
|
21
|
-
});
|
|
22
|
-
/**
|
|
23
|
-
* Validate an app's declared views into the application-service payload.
|
|
24
|
-
* Throws (via Zod) when a view declaration is malformed.
|
|
25
|
-
*/ export function buildViewDeploymentPayload(input) {
|
|
26
|
-
return viewDeploymentPayloadSchema.parse({
|
|
27
|
-
applicationId: input.applicationId,
|
|
28
|
-
views: input.views ?? []
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
//# sourceMappingURL=viewDeployment.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/viewDeployment.ts"],"sourcesContent":["import {z} from 'zod/mini'\n\n/**\n * A view record as persisted to the application service: `type`, `name`, `src`,\n * plus any view-type-specific attributes (passed through for storage).\n */\nconst viewRecordSchema = z.looseObject({\n name: z.string().check(z.regex(/^[a-zA-Z0-9_-]+$/, 'View `name` must match /^[a-zA-Z0-9_-]+$/')),\n src: z.string(),\n type: z.enum(['panel']),\n})\n\n/**\n * Payload registering an app's views with the application service on deploy.\n *\n * Phase 1 stub: the service that stores views does not exist yet, so the\n * payload is validated and logged only — never sent. Builds the contract the\n * application-service endpoint will accept.\n */\nconst viewDeploymentPayloadSchema = z.object({\n applicationId: z.string(),\n views: z.array(viewRecordSchema),\n})\n\ntype ViewDeploymentPayload = z.infer<typeof viewDeploymentPayloadSchema>\n\n/**\n * Validate an app's declared views into the application-service payload.\n * Throws (via Zod) when a view declaration is malformed.\n */\nexport function buildViewDeploymentPayload(input: {\n applicationId: string\n views?: ReadonlyArray<Record<string, unknown>>\n}): ViewDeploymentPayload {\n return viewDeploymentPayloadSchema.parse({\n applicationId: input.applicationId,\n views: input.views ?? [],\n })\n}\n"],"names":["z","viewRecordSchema","looseObject","name","string","check","regex","src","type","enum","viewDeploymentPayloadSchema","object","applicationId","views","array","buildViewDeploymentPayload","input","parse"],"mappings":"AAAA,SAAQA,CAAC,QAAO,WAAU;AAE1B;;;CAGC,GACD,MAAMC,mBAAmBD,EAAEE,WAAW,CAAC;IACrCC,MAAMH,EAAEI,MAAM,GAAGC,KAAK,CAACL,EAAEM,KAAK,CAAC,oBAAoB;IACnDC,KAAKP,EAAEI,MAAM;IACbI,MAAMR,EAAES,IAAI,CAAC;QAAC;KAAQ;AACxB;AAEA;;;;;;CAMC,GACD,MAAMC,8BAA8BV,EAAEW,MAAM,CAAC;IAC3CC,eAAeZ,EAAEI,MAAM;IACvBS,OAAOb,EAAEc,KAAK,CAACb;AACjB;AAIA;;;CAGC,GACD,OAAO,SAASc,2BAA2BC,KAG1C;IACC,OAAON,4BAA4BO,KAAK,CAAC;QACvCL,eAAeI,MAAMJ,aAAa;QAClCC,OAAOG,MAAMH,KAAK,IAAI,EAAE;IAC1B;AACF"}
|