diff --git a/dev/depcheck-test/package.json b/dev/depcheck-test/package.json index 3da85260a2b..f5e27b60773 100644 --- a/dev/depcheck-test/package.json +++ b/dev/depcheck-test/package.json @@ -1,6 +1,6 @@ { "name": "depcheck-test", - "version": "3.62.0", + "version": "3.62.2", "private": true, "license": "MIT", "author": "Sanity.io " diff --git a/dev/design-studio/package.json b/dev/design-studio/package.json index 85cc130603a..82cd27c528e 100644 --- a/dev/design-studio/package.json +++ b/dev/design-studio/package.json @@ -1,6 +1,6 @@ { "name": "design-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Sanity Design Studio", "keywords": [ diff --git a/dev/embedded-studio/package.json b/dev/embedded-studio/package.json index 32c1430bd7f..15abccee07b 100644 --- a/dev/embedded-studio/package.json +++ b/dev/embedded-studio/package.json @@ -1,6 +1,6 @@ { "name": "embedded-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "scripts": { "build": "tsc && vite build && sanity manifest extract", @@ -15,7 +15,7 @@ "styled-components": "^6.1.0" }, "devDependencies": { - "@types/react": "^18.3.11", + "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", "typescript": "5.6.3", diff --git a/dev/page-building-studio/package.json b/dev/page-building-studio/package.json index d99e3dfc41c..31471146248 100644 --- a/dev/page-building-studio/package.json +++ b/dev/page-building-studio/package.json @@ -1,6 +1,6 @@ { "name": "sanity-page-building-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "license": "MIT", "author": "Sanity.io ", diff --git a/dev/starter-next-studio/package.json b/dev/starter-next-studio/package.json index a81b2ba265b..382dd3c4c02 100644 --- a/dev/starter-next-studio/package.json +++ b/dev/starter-next-studio/package.json @@ -1,6 +1,6 @@ { "name": "sanity-starter-next-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "license": "MIT", "author": "Sanity.io ", diff --git a/dev/starter-studio/package.json b/dev/starter-studio/package.json index 6d638bbcbdf..e3fe258ce02 100644 --- a/dev/starter-studio/package.json +++ b/dev/starter-studio/package.json @@ -1,6 +1,6 @@ { "name": "sanity-starter-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "license": "MIT", "author": "Sanity.io ", diff --git a/dev/strict-studio/package.json b/dev/strict-studio/package.json index 7352fcc7553..25dffd0817e 100644 --- a/dev/strict-studio/package.json +++ b/dev/strict-studio/package.json @@ -1,6 +1,6 @@ { "name": "sanity-strict-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "license": "MIT", "author": "Sanity.io ", diff --git a/dev/studio-e2e-testing/package.json b/dev/studio-e2e-testing/package.json index 4693861d364..7d0e262ed07 100644 --- a/dev/studio-e2e-testing/package.json +++ b/dev/studio-e2e-testing/package.json @@ -1,6 +1,6 @@ { "name": "studio-e2e-testing", - "version": "3.62.0", + "version": "3.62.2", "private": true, "keywords": [ "sanity" @@ -18,7 +18,7 @@ "@sanity/google-maps-input": "^4.0.0", "@sanity/icons": "^3.4.0", "@sanity/ui": "^2.8.10", - "@sanity/vision": "3.62.0", + "@sanity/vision": "3.62.2", "react": "^18.3.1", "react-dom": "^18.3.1", "sanity": "workspace:*", diff --git a/dev/test-create-integration-studio/.depcheckignore.json b/dev/test-create-integration-studio/.depcheckignore.json new file mode 100644 index 00000000000..0958cae05dc --- /dev/null +++ b/dev/test-create-integration-studio/.depcheckignore.json @@ -0,0 +1,3 @@ +{ + "ignore": ["styled-components", "react", "react-dom", "sanity"] +} diff --git a/dev/test-create-integration-studio/.gitignore b/dev/test-create-integration-studio/.gitignore new file mode 100644 index 00000000000..9b1c8b133c9 --- /dev/null +++ b/dev/test-create-integration-studio/.gitignore @@ -0,0 +1 @@ +/dist diff --git a/dev/test-create-integration-studio/package.json b/dev/test-create-integration-studio/package.json new file mode 100644 index 00000000000..ad3b6ccf5cd --- /dev/null +++ b/dev/test-create-integration-studio/package.json @@ -0,0 +1,22 @@ +{ + "name": "test-create-integration-studio", + "version": "3.61.0", + "private": true, + "license": "MIT", + "author": "Sanity.io ", + "scripts": { + "build": "../.bin/sanity build", + "clean": "rimraf .sanity dist", + "deploy": "npx sanity deploy", + "dev": "../.bin/sanity dev", + "lint": "eslint .", + "start": "../.bin/sanity start" + }, + "dependencies": { + "@sanity/code-input": "^4.1.4", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "sanity": "workspace:*", + "styled-components": "^6.1.0" + } +} diff --git a/dev/test-create-integration-studio/sanity.cli.ts b/dev/test-create-integration-studio/sanity.cli.ts new file mode 100644 index 00000000000..89fd9fa0307 --- /dev/null +++ b/dev/test-create-integration-studio/sanity.cli.ts @@ -0,0 +1,11 @@ +import {defineCliConfig} from 'sanity/cli' + +export default defineCliConfig({ + api: { + projectId: 'ppsg7ml5', + dataset: 'test', + }, + + studioHost: 'create-integration-test', + autoUpdates: false, +}) diff --git a/dev/test-create-integration-studio/sanity.config.ts b/dev/test-create-integration-studio/sanity.config.ts new file mode 100644 index 00000000000..146861de727 --- /dev/null +++ b/dev/test-create-integration-studio/sanity.config.ts @@ -0,0 +1,22 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import {codeInput} from '@sanity/code-input' +import {defineConfig} from 'sanity' +import {structureTool} from 'sanity/structure' + +import {schemaTypes} from './schema' + +export default defineConfig({ + plugins: [structureTool(), codeInput()], + title: 'Strict', + name: 'default', + projectId: 'ppsg7ml5', + dataset: 'test', + schema: {types: schemaTypes}, + + beta: { + create: { + startInCreateEnabled: true, + fallbackStudioOrigin: 'create-integration-test.sanity.studio', + }, + }, +}) diff --git a/dev/test-create-integration-studio/schema.ts b/dev/test-create-integration-studio/schema.ts new file mode 100644 index 00000000000..70edcbcb80d --- /dev/null +++ b/dev/test-create-integration-studio/schema.ts @@ -0,0 +1,354 @@ +import {defineArrayMember, defineField, defineType, type FieldGroupDefinition} from 'sanity' + +export const mainGroup: FieldGroupDefinition = { + name: 'main', + title: 'Common fields', +} +export const seoGroup: FieldGroupDefinition = { + name: 'seo', + title: 'SEO fields', +} + +export const schemaTypes = [ + defineType({ + title: 'Documentation Article', + name: 'create-test-article', + type: 'document', + groups: [mainGroup, seoGroup], + fieldsets: [{name: 'switches', title: 'Article settings', options: {columns: 2}}], + fields: [ + defineField({ + name: 'title', + title: 'Title', + type: 'string', + description: 'Main header and page title.', + group: [mainGroup.name], + }), + defineField({ + name: 'description', + title: 'Description', + type: 'text', + rows: 3, + description: 'Lede and page summary.', + group: [mainGroup.name], + }), + defineField({ + type: 'image', + name: 'image', + title: 'Image', + description: 'Primary image for content.', + group: [mainGroup.name], + }), + defineField({ + name: 'authors', + title: 'Authors', + type: 'array', + description: 'One or more content authors', + validation: (Rule) => Rule.required().min(1).unique(), + of: [ + defineArrayMember({ + type: 'object', + name: 'authors', + title: 'Authors', + fields: [ + defineField({ + type: 'string', + name: 'name', + title: 'Full Name', + }), + defineField({ + type: 'string', + name: 'email', + title: 'Email', + }), + ], + }), + ], + group: [mainGroup.name], + }), + { + title: 'Slug', + name: 'slug', + type: 'slug', + description: 'Last part of the page URL.', + options: { + source: 'title', + auto: true, + }, + }, + { + title: 'Hide this article?', + name: 'hidden', + type: 'boolean', + description: 'Turn this on to prevent this document from showing up in search results.', + }, + { + title: 'Enterprise Feature', + name: 'enterprise', + type: 'boolean', + description: 'This article describes a feature only available on enterprise plans', + }, + { + title: 'Experimental Feature', + name: 'experimental', + type: 'boolean', + description: + 'This article describes a feature that should be considered experimental, where the API and feature set might change', + }, + { + title: 'Body', + name: 'body', + type: 'blockContent', + }, + { + title: 'Search keywords', + name: 'keywords', + type: 'array', + of: [{type: 'string'}], + options: { + layout: 'tags', + }, + description: 'A list of keywords to supplement search index.', + }, + { + title: 'Related Articles', + name: 'articles', + type: 'array', + of: [ + { + type: 'reference', + to: [{type: 'create-test-article'}], + }, + ], + }, + defineField({ + name: 'seoTitle', + title: 'SEO Title', + type: 'string', + description: 'Will override title used for SEO and social media previews.', + group: [seoGroup.name], + }), + defineField({ + name: 'seoDescription', + title: 'SEO Description', + type: 'text', + rows: 3, + description: 'Will override description used for SEO and social media previews.', + group: [seoGroup.name], + }), + defineField({ + name: 'seoImage', + title: 'SEO Image', + type: 'image', + description: 'Will override image used for SEO and social media previews.', + group: [seoGroup.name], + }), + ], + }), + defineType({ + title: 'Block Content', + name: 'blockContent', + type: 'array', + of: [ + { + title: 'Block', + type: 'block', + styles: [ + {title: 'Normal', value: 'normal'}, + {title: 'H1', value: 'h1'}, + {title: 'H2', value: 'h2'}, + {title: 'H3', value: 'h3'}, + {title: 'H4', value: 'h4'}, + {title: 'Quote', value: 'blockquote'}, + ], + marks: { + decorators: [ + {title: 'Strong', value: 'strong'}, + {title: 'Emphasis', value: 'em'}, + {title: 'Code', value: 'code'}, + ], + annotations: [ + { + title: 'Abbreviation', + name: 'abbreviation', + type: 'object', + description: 'Add definitions for abbreviations, initialisms, and acronyms', + fields: [ + { + title: 'Expansion', + name: 'title', + type: 'string', + description: 'Spell out the full term', + }, + ], + }, + ], + }, + }, + { + title: 'Call to action', + name: 'callToAction', + type: 'object', + fields: [ + { + title: 'Label', + name: 'label', + type: 'string', + }, + { + title: 'Url', + name: 'url', + type: 'string', + }, + ], + }, + { + title: 'Image', + type: 'image', + options: { + hotspot: true, + }, + preview: { + select: { + imageUrl: 'asset.url', + title: 'caption', + }, + }, + fields: [ + { + title: 'Caption', + name: 'caption', + type: 'string', + }, + { + name: 'alt', + type: 'string', + title: 'Alt text', + description: 'Alternative text for screenreaders. Falls back on caption if not set', + }, + { + title: 'Enable lightbox', + description: + '❓ Optional. The default behavior is to enable it if image is large enough to benefit from it.', + name: 'enableLightbox', + type: 'boolean', + }, + { + title: 'Icon', + name: 'isIcon', + type: 'boolean', + }, + { + title: 'Disable shadow', + description: 'Not implemented in most surfaces.', + name: 'disableShadow', + type: 'boolean', + }, + { + title: 'Large', + description: 'Not implemented in most surfaces.', + name: 'isLarge', + type: 'boolean', + }, + { + name: 'infoBox', + title: 'Info Box', + type: 'object', + fields: [ + { + name: 'title', + title: 'Title', + type: 'string', + }, + ], + }, + ], + }, + { + name: 'infoBox', + title: 'Info Box', + type: 'object', + fields: [ + { + name: 'title', + title: 'Title', + type: 'string', + }, + { + title: 'Box Content', + name: 'body', + type: 'text', + }, + ], + preview: { + select: { + title: 'title', + body: 'body', + }, + prepare(selection) { + return selection + }, + }, + }, + {name: 'code', type: 'code'}, + { + name: 'protip', + type: 'object', + fields: [ + { + title: 'Protip', + name: 'body', + type: 'text', + }, + ], + preview: { + select: { + body: 'body', + }, + prepare(selection) { + return selection + }, + }, + }, + { + name: 'gotcha', + type: 'object', + fields: [ + { + title: 'Gotcha', + name: 'body', + type: 'text', + }, + ], + preview: { + select: { + body: 'body', + }, + prepare(selection) { + return selection + }, + }, + }, + { + name: 'example', + type: 'object', + fields: [ + { + title: 'Example', + name: 'body', + description: 'Use this to exemplify something that’s not just a code block', + type: 'text', + }, + ], + preview: { + select: { + body: 'body', + }, + prepare(selection) { + return selection + }, + }, + }, + ], + }), +] diff --git a/dev/test-create-integration-studio/tsconfig.json b/dev/test-create-integration-studio/tsconfig.json new file mode 100644 index 00000000000..e10fe0a8eaa --- /dev/null +++ b/dev/test-create-integration-studio/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.dev" +} diff --git a/dev/test-next-studio/package.json b/dev/test-next-studio/package.json index d32940afbc6..3bca353d568 100644 --- a/dev/test-next-studio/package.json +++ b/dev/test-next-studio/package.json @@ -1,6 +1,6 @@ { "name": "sanity-test-next-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "license": "MIT", "author": "Sanity.io ", @@ -13,7 +13,7 @@ "dependencies": { "@sanity/vision": "workspace:*", "babel-plugin-react-compiler": "0.0.0-experimental-de2cfda-20240912", - "next": "15.0.0", + "next": "15.0.1", "react": "19.0.0-rc-a7d1240c-20240731", "react-dom": "19.0.0-rc-a7d1240c-20240731", "react-is": "19.0.0-rc-a7d1240c-20240731", diff --git a/dev/test-studio/package.json b/dev/test-studio/package.json index 5c8363cddc6..02b01e90861 100644 --- a/dev/test-studio/package.json +++ b/dev/test-studio/package.json @@ -1,6 +1,6 @@ { "name": "sanity-test-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "license": "MIT", "author": "Sanity.io ", @@ -16,10 +16,10 @@ "workshop:dev": "node -r esbuild-register scripts/workshop/dev.ts" }, "dependencies": { - "@portabletext/editor": "^1.1.5", + "@portabletext/editor": "^1.1.7", "@portabletext/react": "^3.0.0", "@sanity/assist": "^3.0.2", - "@sanity/block-tools": "3.62.0", + "@sanity/block-tools": "3.62.2", "@sanity/client": "^6.22.2", "@sanity/color": "^3.0.0", "@sanity/google-maps-input": "^4.0.0", @@ -41,7 +41,7 @@ "@sanity/util": "workspace:*", "@sanity/uuid": "^3.0.1", "@sanity/vision": "workspace:*", - "@sanity/visual-editing": "2.2.2", + "@sanity/visual-editing": "2.4.1", "@turf/helpers": "^6.0.1", "@turf/points-within-polygon": "^5.1.5", "@vercel/stega": "0.1.2", diff --git a/examples/blog-studio/package.json b/examples/blog-studio/package.json index 63d5aec0f24..e46480cb749 100644 --- a/examples/blog-studio/package.json +++ b/examples/blog-studio/package.json @@ -1,6 +1,6 @@ { "name": "blog-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Content studio running with schema from the blog init template", "keywords": [ diff --git a/examples/clean-studio/package.json b/examples/clean-studio/package.json index 161d1701c2c..50fd9a2e8e2 100644 --- a/examples/clean-studio/package.json +++ b/examples/clean-studio/package.json @@ -1,6 +1,6 @@ { "name": "clean-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Content studio running with schema from the clean template", "keywords": [ diff --git a/examples/ecommerce-studio/package.json b/examples/ecommerce-studio/package.json index 73602f75c39..02f752a396c 100644 --- a/examples/ecommerce-studio/package.json +++ b/examples/ecommerce-studio/package.json @@ -1,6 +1,6 @@ { "name": "ecommerce-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "", "keywords": [ @@ -29,7 +29,7 @@ "start": "sanity dev --port 3337" }, "dependencies": { - "@sanity/cli": "3.62.0", + "@sanity/cli": "3.62.2", "@sanity/ui": "^2.8.10", "react": "^18.3.1", "react-barcode": "^1.4.1", diff --git a/examples/movies-studio/package.json b/examples/movies-studio/package.json index bd75085293d..02a1af3d8f0 100644 --- a/examples/movies-studio/package.json +++ b/examples/movies-studio/package.json @@ -1,6 +1,6 @@ { "name": "movies-studio", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Content studio running with schema from the moviedb init template", "keywords": [ diff --git a/lerna.json b/lerna.json index 5817b8a5f65..d2d03fe44d2 100644 --- a/lerna.json +++ b/lerna.json @@ -12,5 +12,5 @@ "packages/groq", "packages/sanity" ], - "version": "3.62.0" + "version": "3.62.2" } diff --git a/packages/@repo/dev-aliases/package.json b/packages/@repo/dev-aliases/package.json index 3ae2ee8b676..47d7a79cca7 100644 --- a/packages/@repo/dev-aliases/package.json +++ b/packages/@repo/dev-aliases/package.json @@ -1,6 +1,6 @@ { "name": "@repo/dev-aliases", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Dev aliases for the sanity monorepo", "type": "module", diff --git a/packages/@repo/package.bundle/package.json b/packages/@repo/package.bundle/package.json index a17cbaea3fa..67691b6150e 100644 --- a/packages/@repo/package.bundle/package.json +++ b/packages/@repo/package.bundle/package.json @@ -1,6 +1,6 @@ { "name": "@repo/package.bundle", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Shared package bundle configuration", "main": "./src/package.bundle.ts", diff --git a/packages/@repo/package.config/package.json b/packages/@repo/package.config/package.json index 28df3897777..8d7c92ee6b0 100644 --- a/packages/@repo/package.config/package.json +++ b/packages/@repo/package.config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/package.config", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Shared @sanity/pkg-utils configuration", "main": "./src/package.config.ts", diff --git a/packages/@repo/test-config/package.json b/packages/@repo/test-config/package.json index 4ced85c458f..5deff7e3cc4 100644 --- a/packages/@repo/test-config/package.json +++ b/packages/@repo/test-config/package.json @@ -1,6 +1,6 @@ { "name": "@repo/test-config", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Test (as in unit test) config shared across packages in the sanity monorepo", "type": "module", diff --git a/packages/@repo/test-exports/package.json b/packages/@repo/test-exports/package.json index 744afdae4c3..9f182ae6d4a 100644 --- a/packages/@repo/test-exports/package.json +++ b/packages/@repo/test-exports/package.json @@ -1,6 +1,6 @@ { "name": "@repo/test-exports", - "version": "3.62.0", + "version": "3.62.2", "private": true, "description": "Ensures that all the monorepo packages that are published works in native node ESM and CJS runtimes", "exports": { diff --git a/packages/@repo/tsconfig/package.json b/packages/@repo/tsconfig/package.json index abed1d00dfe..d9dc6cd66e5 100644 --- a/packages/@repo/tsconfig/package.json +++ b/packages/@repo/tsconfig/package.json @@ -1,5 +1,5 @@ { "name": "@repo/tsconfig", - "version": "3.62.0", + "version": "3.62.2", "private": true } diff --git a/packages/@sanity/block-tools/package.json b/packages/@sanity/block-tools/package.json index 65aef59690e..146337e5024 100644 --- a/packages/@sanity/block-tools/package.json +++ b/packages/@sanity/block-tools/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/block-tools", - "version": "3.62.0", + "version": "3.62.2", "description": "Can format HTML, Slate JSON or Sanity block array into any other format.", "keywords": [ "sanity", @@ -49,7 +49,7 @@ "watch": "pkg-utils watch" }, "dependencies": { - "@sanity/types": "3.62.0", + "@sanity/types": "3.62.2", "@types/react": "^18.3.5", "get-random-values-esm": "1.0.2", "lodash": "^4.17.21" @@ -57,7 +57,7 @@ "devDependencies": { "@repo/package.config": "workspace:*", "@repo/test-config": "workspace:*", - "@sanity/schema": "3.62.0", + "@sanity/schema": "3.62.2", "@types/jsdom": "^20.0.0", "@types/lodash": "^4.17.7", "@vercel/stega": "0.1.2", diff --git a/packages/@sanity/cli/package.json b/packages/@sanity/cli/package.json index b76453d8b26..60f61de8f93 100644 --- a/packages/@sanity/cli/package.json +++ b/packages/@sanity/cli/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/cli", - "version": "3.62.0", + "version": "3.62.2", "description": "Sanity CLI tool for managing Sanity installations, managing plugins, schemas and datasets", "keywords": [ "sanity", @@ -58,9 +58,9 @@ "dependencies": { "@babel/traverse": "^7.23.5", "@sanity/client": "^6.22.2", - "@sanity/codegen": "3.62.0", + "@sanity/codegen": "3.62.2", "@sanity/telemetry": "^0.7.7", - "@sanity/util": "3.62.0", + "@sanity/util": "3.62.2", "chalk": "^4.1.2", "debug": "^4.3.4", "decompress": "^4.2.0", diff --git a/packages/@sanity/cli/src/actions/init-project/initProject.ts b/packages/@sanity/cli/src/actions/init-project/initProject.ts index dac97466649..691a327039d 100644 --- a/packages/@sanity/cli/src/actions/init-project/initProject.ts +++ b/packages/@sanity/cli/src/actions/init-project/initProject.ts @@ -4,11 +4,13 @@ import path from 'node:path' import {type DatasetAclMode, type SanityProject} from '@sanity/client' import {type Framework} from '@vercel/frameworks' +import {type detectFrameworkRecord} from '@vercel/fs-detectors' import dotenv from 'dotenv' import execa, {type CommonOptions} from 'execa' import {deburr, noop} from 'lodash' import pFilter from 'p-filter' import resolveFrom from 'resolve-from' +import semver from 'semver' import {evaluate, patch} from 'silver-fleece' import which from 'which' @@ -55,6 +57,7 @@ import { promptForNextTemplate, promptForStudioPath, } from './prompts/nextjs' +import {readPackageJson} from './readPackageJson' import {reconfigureV2Project} from './reconfigureV2Project' import templates from './templates' import { @@ -110,7 +113,9 @@ export interface ProjectOrganization { // eslint-disable-next-line max-statements, complexity export default async function initSanity( args: CliCommandArguments, - context: CliCommandContext & {detectedFramework: Framework | null}, + context: CliCommandContext & { + detectedFramework: Awaited> + }, ): Promise { const { output, @@ -128,6 +133,8 @@ export default async function initSanity( const cliFlags = args.extOptions const unattended = cliFlags.y || cliFlags.yes const print = unattended ? noop : output.print + const warn = (msg: string) => output.warn(chalk.yellow.bgBlack(msg)) + const intendedPlan = cliFlags['project-plan'] const intendedCoupon = cliFlags.coupon const reconfigure = cliFlags.reconfigure @@ -298,7 +305,8 @@ export default async function initSanity( } let initNext = false - if (detectedFramework?.slug === 'nextjs') { + const isNextJs = detectedFramework?.slug === 'nextjs' + if (isNextJs) { initNext = await prompt.single({ type: 'confirm', message: @@ -327,6 +335,26 @@ export default async function initSanity( // Ensure we are using the output path provided by user outputPath = answers.outputPath + if (isNextJs) { + const packageJson = readPackageJson(`${outputPath}/package.json`) + const reactVersion = packageJson?.dependencies?.react + + if (reactVersion) { + const isUsingReact19 = semver.coerce(reactVersion)?.major === 19 + const isUsingNextJs15 = semver.coerce(detectedFramework?.detectedVersion)?.major === 15 + + if (isUsingNextJs15 && isUsingReact19) { + warn('╭────────────────────────────────────────────────────────────╮') + warn('│ │') + warn('│ It looks like you are using Next.js 15 and React 19 │') + warn('│ Please read our compatibility guide. │') + warn('│ https://www.sanity.io/help/react-19 │') + warn('│ │') + warn('╰────────────────────────────────────────────────────────────╯') + } + } + } + if (initNext) { const useTypeScript = unattended ? true : await promptForTypeScript(prompt) trace.log({step: 'useTypeScript', selectedOption: useTypeScript ? 'yes' : 'no'}) diff --git a/packages/@sanity/cli/src/actions/init-project/readPackageJson.ts b/packages/@sanity/cli/src/actions/init-project/readPackageJson.ts new file mode 100644 index 00000000000..ed638458a67 --- /dev/null +++ b/packages/@sanity/cli/src/actions/init-project/readPackageJson.ts @@ -0,0 +1,18 @@ +import fs from 'node:fs' + +import {type PackageJson} from '../../types' + +/** + * Read the `package.json` file at the given path + * + * @param filePath - Path to package.json to read + * @returns The parsed package.json + */ +export function readPackageJson(filePath: string): PackageJson | undefined { + try { + // eslint-disable-next-line no-sync + return JSON.parse(fs.readFileSync(filePath, 'utf8')) + } catch (err) { + return undefined + } +} diff --git a/packages/@sanity/codegen/package.json b/packages/@sanity/codegen/package.json index abdedc2d3b9..61323638e28 100644 --- a/packages/@sanity/codegen/package.json +++ b/packages/@sanity/codegen/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/codegen", - "version": "3.62.0", + "version": "3.62.2", "description": "Codegen toolkit for Sanity.io", "keywords": [ "sanity", diff --git a/packages/@sanity/diff/package.json b/packages/@sanity/diff/package.json index d23d98a9a79..e430dca2186 100644 --- a/packages/@sanity/diff/package.json +++ b/packages/@sanity/diff/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/diff", - "version": "3.62.0", + "version": "3.62.2", "description": "Generates diffs between documents and primitive types", "keywords": [ "sanity", diff --git a/packages/@sanity/migrate/package.json b/packages/@sanity/migrate/package.json index d9b57cc4e93..fe92dddc0ad 100644 --- a/packages/@sanity/migrate/package.json +++ b/packages/@sanity/migrate/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/migrate", - "version": "3.62.0", + "version": "3.62.2", "description": "Tooling for running data migrations on Sanity.io projects", "keywords": [ "sanity", @@ -52,8 +52,8 @@ "dependencies": { "@sanity/client": "^6.22.2", "@sanity/mutate": "^0.10.1", - "@sanity/types": "3.62.0", - "@sanity/util": "3.62.0", + "@sanity/types": "3.62.2", + "@sanity/util": "3.62.2", "arrify": "^2.0.1", "debug": "^4.3.4", "fast-fifo": "^1.3.2", diff --git a/packages/@sanity/mutator/package.json b/packages/@sanity/mutator/package.json index c8dab966c72..db92aa56702 100644 --- a/packages/@sanity/mutator/package.json +++ b/packages/@sanity/mutator/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/mutator", - "version": "3.62.0", + "version": "3.62.2", "description": "A set of models to make it easier to utilize the powerful real time collaborative features of Sanity", "keywords": [ "sanity", @@ -50,7 +50,7 @@ }, "dependencies": { "@sanity/diff-match-patch": "^3.1.1", - "@sanity/types": "3.62.0", + "@sanity/types": "3.62.2", "@sanity/uuid": "^3.0.1", "debug": "^4.3.4", "lodash": "^4.17.21" diff --git a/packages/@sanity/schema/package.json b/packages/@sanity/schema/package.json index f572966a64f..88103ba6df3 100644 --- a/packages/@sanity/schema/package.json +++ b/packages/@sanity/schema/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/schema", - "version": "3.62.0", + "version": "3.62.2", "description": "", "keywords": [ "sanity", @@ -64,7 +64,7 @@ }, "dependencies": { "@sanity/generate-help-url": "^3.0.0", - "@sanity/types": "3.62.0", + "@sanity/types": "3.62.2", "arrify": "^1.0.1", "groq-js": "^1.13.0", "humanize-list": "^1.0.1", diff --git a/packages/@sanity/types/package.json b/packages/@sanity/types/package.json index 60a5923c99f..73b605f3f34 100644 --- a/packages/@sanity/types/package.json +++ b/packages/@sanity/types/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/types", - "version": "3.62.0", + "version": "3.62.2", "description": "Type definitions for common Sanity data structures", "keywords": [ "sanity", @@ -55,7 +55,7 @@ "devDependencies": { "@repo/package.config": "workspace:*", "@repo/test-config": "workspace:*", - "@sanity/insert-menu": "1.0.9", + "@sanity/insert-menu": "1.0.10", "@vitejs/plugin-react": "^4.3.1", "react": "^18.3.1", "rimraf": "^3.0.2", diff --git a/packages/@sanity/types/src/reference/types.ts b/packages/@sanity/types/src/reference/types.ts index 8a2e51a00a4..89d3a9ce472 100644 --- a/packages/@sanity/types/src/reference/types.ts +++ b/packages/@sanity/types/src/reference/types.ts @@ -2,6 +2,7 @@ import {type SanityClient} from '@sanity/client' import {type SanityDocument} from '../documents' import {type Path} from '../paths' +import {type BaseSchemaTypeOptions} from '../schema' /** @public */ export interface Reference { @@ -56,7 +57,7 @@ export interface ReferenceFilterQueryOptions { } /** @public */ -export interface ReferenceBaseOptions { +export interface ReferenceBaseOptions extends BaseSchemaTypeOptions { disableNew?: boolean } diff --git a/packages/@sanity/types/src/schema/definition/type/array.ts b/packages/@sanity/types/src/schema/definition/type/array.ts index efecfdc1cfb..2a966859728 100644 --- a/packages/@sanity/types/src/schema/definition/type/array.ts +++ b/packages/@sanity/types/src/schema/definition/type/array.ts @@ -8,12 +8,17 @@ import { type IntrinsicTypeName, type TypeAliasDefinition, } from '../schemaDefinition' -import {type BaseSchemaDefinition, type SearchConfiguration, type TitledListValue} from './common' +import { + type BaseSchemaDefinition, + type BaseSchemaTypeOptions, + type SearchConfiguration, + type TitledListValue, +} from './common' export type {InsertMenuOptions} /** @public */ -export interface ArrayOptions extends SearchConfiguration { +export interface ArrayOptions extends SearchConfiguration, BaseSchemaTypeOptions { list?: TitledListValue[] | V[] // inferring the array.of value for ArrayDefinition cause too much code-noise and was removed. // Since we don't have the type-info needed here, we allow values diff --git a/packages/@sanity/types/src/schema/definition/type/block.ts b/packages/@sanity/types/src/schema/definition/type/block.ts index d1613dd53dd..7c045b7e58c 100644 --- a/packages/@sanity/types/src/schema/definition/type/block.ts +++ b/packages/@sanity/types/src/schema/definition/type/block.ts @@ -3,13 +3,13 @@ import {type ComponentType, type ReactNode} from 'react' import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' import {type ArrayOfType} from './array' -import {type BaseSchemaDefinition} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions} from './common' import {type ObjectDefinition} from './object' /** * Schema options for a Block schema definition * @public */ -export interface BlockOptions { +export interface BlockOptions extends BaseSchemaTypeOptions { /** * Turn on or off the builtin browser spellchecking. Default is on. */ diff --git a/packages/@sanity/types/src/schema/definition/type/boolean.ts b/packages/@sanity/types/src/schema/definition/type/boolean.ts index 9c5bf9ca6ae..30c0ef83ffd 100644 --- a/packages/@sanity/types/src/schema/definition/type/boolean.ts +++ b/packages/@sanity/types/src/schema/definition/type/boolean.ts @@ -1,9 +1,9 @@ import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions} from './common' /** @public */ -export interface BooleanOptions { +export interface BooleanOptions extends BaseSchemaTypeOptions { layout?: 'switch' | 'checkbox' } diff --git a/packages/@sanity/types/src/schema/definition/type/common.ts b/packages/@sanity/types/src/schema/definition/type/common.ts index 91e1c638965..b970f82e3fe 100644 --- a/packages/@sanity/types/src/schema/definition/type/common.ts +++ b/packages/@sanity/types/src/schema/definition/type/common.ts @@ -24,6 +24,33 @@ export type FieldGroupDefinition = { i18n?: I18nTextRecord<'title'> } +/** + * Options for configuring how Sanity Create interfaces with the type or field. + * + * @public + */ +export interface SanityCreateOptions { + /** Set to true to exclude a type or field from appearing in Sanity Create */ + exclude?: boolean + + /** + * A short description of what the type or field is used for. + * Purpose can be used to improve how and when content mapping uses the field. + * */ + purpose?: string +} + +/** + * `BaseOptions` applies to all type options. + * + * It can be extended by interface declaration merging in plugins to provide generic options to all types and fields. + * + * @public + * */ +export interface BaseSchemaTypeOptions { + sanityCreate?: SanityCreateOptions +} + /** @public */ export interface BaseSchemaDefinition { name: string diff --git a/packages/@sanity/types/src/schema/definition/type/date.ts b/packages/@sanity/types/src/schema/definition/type/date.ts index 7a234ffc00b..b76e17009dd 100644 --- a/packages/@sanity/types/src/schema/definition/type/date.ts +++ b/packages/@sanity/types/src/schema/definition/type/date.ts @@ -1,10 +1,10 @@ import {type FieldReference} from '../../../validation' import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions} from './common' /** @public */ -export interface DateOptions { +export interface DateOptions extends BaseSchemaTypeOptions { dateFormat?: string } diff --git a/packages/@sanity/types/src/schema/definition/type/datetime.ts b/packages/@sanity/types/src/schema/definition/type/datetime.ts index dcc102b2112..7cc85c042f0 100644 --- a/packages/@sanity/types/src/schema/definition/type/datetime.ts +++ b/packages/@sanity/types/src/schema/definition/type/datetime.ts @@ -1,10 +1,10 @@ import {type FieldReference} from '../../../validation' import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions} from './common' /** @public */ -export interface DatetimeOptions { +export interface DatetimeOptions extends BaseSchemaTypeOptions { dateFormat?: string timeFormat?: string timeStep?: number diff --git a/packages/@sanity/types/src/schema/definition/type/document.ts b/packages/@sanity/types/src/schema/definition/type/document.ts index 0783ada5cee..e567dd18b42 100644 --- a/packages/@sanity/types/src/schema/definition/type/document.ts +++ b/packages/@sanity/types/src/schema/definition/type/document.ts @@ -1,6 +1,7 @@ import {type SanityDocument} from '../../../documents/types' import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty, type SortOrdering} from '../../types' +import {type BaseSchemaTypeOptions} from './common' import {type ObjectDefinition} from './object' /** @@ -9,7 +10,7 @@ import {type ObjectDefinition} from './object' * @public */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface DocumentOptions {} +export interface DocumentOptions extends BaseSchemaTypeOptions {} /** @public */ // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/packages/@sanity/types/src/schema/definition/type/email.ts b/packages/@sanity/types/src/schema/definition/type/email.ts index d97efbbedd6..001c2a7672a 100644 --- a/packages/@sanity/types/src/schema/definition/type/email.ts +++ b/packages/@sanity/types/src/schema/definition/type/email.ts @@ -1,6 +1,6 @@ import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions} from './common' /** @public */ // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -9,7 +9,7 @@ export interface EmailRule extends RuleDef {} /** @public */ // only exists to support declaration extensions // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface EmailOptions {} +export interface EmailOptions extends BaseSchemaTypeOptions {} /** @public */ export interface EmailDefinition extends BaseSchemaDefinition { diff --git a/packages/@sanity/types/src/schema/definition/type/geopoint.ts b/packages/@sanity/types/src/schema/definition/type/geopoint.ts index fa505eac894..2e3d00bfac3 100644 --- a/packages/@sanity/types/src/schema/definition/type/geopoint.ts +++ b/packages/@sanity/types/src/schema/definition/type/geopoint.ts @@ -1,6 +1,6 @@ import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions} from './common' /** * Geographical point representing a pair of latitude and longitude coordinates, @@ -37,7 +37,7 @@ export interface GeopointRule extends RuleDef {} /** @public */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface GeopointOptions {} +export interface GeopointOptions extends BaseSchemaTypeOptions {} /** @public */ export interface GeopointDefinition extends BaseSchemaDefinition { diff --git a/packages/@sanity/types/src/schema/definition/type/number.ts b/packages/@sanity/types/src/schema/definition/type/number.ts index 19082922544..939383f240c 100644 --- a/packages/@sanity/types/src/schema/definition/type/number.ts +++ b/packages/@sanity/types/src/schema/definition/type/number.ts @@ -1,11 +1,11 @@ import {type FieldReference} from '../../../validation' import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition, type EnumListProps} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions, type EnumListProps} from './common' /** @public */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface NumberOptions extends EnumListProps {} +export interface NumberOptions extends EnumListProps, BaseSchemaTypeOptions {} /** @public */ export interface NumberRule extends RuleDef { diff --git a/packages/@sanity/types/src/schema/definition/type/object.ts b/packages/@sanity/types/src/schema/definition/type/object.ts index 3f8d94226b0..443bdf5d830 100644 --- a/packages/@sanity/types/src/schema/definition/type/object.ts +++ b/packages/@sanity/types/src/schema/definition/type/object.ts @@ -4,12 +4,13 @@ import {type InitialValueProperty} from '../../types' import {type FieldDefinition} from '../schemaDefinition' import { type BaseSchemaDefinition, + type BaseSchemaTypeOptions, type FieldGroupDefinition, type FieldsetDefinition, } from './common' /** @public */ -export interface ObjectOptions { +export interface ObjectOptions extends BaseSchemaTypeOptions { collapsible?: boolean collapsed?: boolean columns?: number diff --git a/packages/@sanity/types/src/schema/definition/type/slug.ts b/packages/@sanity/types/src/schema/definition/type/slug.ts index ca6edcc0242..cf1accd73d9 100644 --- a/packages/@sanity/types/src/schema/definition/type/slug.ts +++ b/packages/@sanity/types/src/schema/definition/type/slug.ts @@ -3,7 +3,7 @@ import {type SlugifierFn, type SlugSourceFn} from '../../../slug' import {type SlugIsUniqueValidator} from '../../../validation' import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions} from './common' /** @public */ export interface SlugValue { @@ -16,7 +16,7 @@ export interface SlugValue { export interface SlugRule extends RuleDef {} /** @public */ -export interface SlugOptions { +export interface SlugOptions extends BaseSchemaTypeOptions { source?: string | Path | SlugSourceFn maxLength?: number slugify?: SlugifierFn diff --git a/packages/@sanity/types/src/schema/definition/type/string.ts b/packages/@sanity/types/src/schema/definition/type/string.ts index 0b708079e3b..dd7ed8034eb 100644 --- a/packages/@sanity/types/src/schema/definition/type/string.ts +++ b/packages/@sanity/types/src/schema/definition/type/string.ts @@ -1,11 +1,19 @@ import {type FieldReference} from '../../../validation' import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition, type EnumListProps, type SearchConfiguration} from './common' +import { + type BaseSchemaDefinition, + type BaseSchemaTypeOptions, + type EnumListProps, + type SearchConfiguration, +} from './common' /** @public */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface StringOptions extends EnumListProps, SearchConfiguration {} +export interface StringOptions + extends EnumListProps, + SearchConfiguration, + BaseSchemaTypeOptions {} /** @public */ export interface StringRule extends RuleDef { diff --git a/packages/@sanity/types/src/schema/definition/type/url.ts b/packages/@sanity/types/src/schema/definition/type/url.ts index a8c6e2554bc..906314b21fd 100644 --- a/packages/@sanity/types/src/schema/definition/type/url.ts +++ b/packages/@sanity/types/src/schema/definition/type/url.ts @@ -1,7 +1,7 @@ import {type UriValidationOptions} from '../../../validation/types' import {type RuleDef, type ValidationBuilder} from '../../ruleBuilder' import {type InitialValueProperty} from '../../types' -import {type BaseSchemaDefinition} from './common' +import {type BaseSchemaDefinition, type BaseSchemaTypeOptions} from './common' /** @public */ export interface UrlRule extends RuleDef { @@ -11,7 +11,7 @@ export interface UrlRule extends RuleDef { /** @public */ // only exists to support declaration extensions // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface UrlOptions {} +export interface UrlOptions extends BaseSchemaTypeOptions {} /** @public */ export interface UrlDefinition extends BaseSchemaDefinition { diff --git a/packages/@sanity/types/test/boolean.test.ts b/packages/@sanity/types/test/boolean.test.ts index 1ba1a92abc8..5cb0fc796a3 100644 --- a/packages/@sanity/types/test/boolean.test.ts +++ b/packages/@sanity/types/test/boolean.test.ts @@ -29,6 +29,9 @@ describe('boolean types', () => { hidden: () => false, options: { layout: 'checkbox', + sanityCreate: { + exclude: true, + }, }, }) diff --git a/packages/@sanity/util/package.json b/packages/@sanity/util/package.json index 6eeccc41d0a..5b689e4e7c6 100644 --- a/packages/@sanity/util/package.json +++ b/packages/@sanity/util/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/util", - "version": "3.62.0", + "version": "3.62.2", "description": "Utilities shared across projects of Sanity", "keywords": [ "sanity", @@ -122,7 +122,7 @@ }, "dependencies": { "@sanity/client": "^6.22.2", - "@sanity/types": "3.62.0", + "@sanity/types": "3.62.2", "get-random-values-esm": "1.0.2", "moment": "^2.30.1", "rxjs": "^7.8.1" diff --git a/packages/@sanity/vision/package.json b/packages/@sanity/vision/package.json index 786d115f9bd..872b23cc247 100644 --- a/packages/@sanity/vision/package.json +++ b/packages/@sanity/vision/package.json @@ -1,6 +1,6 @@ { "name": "@sanity/vision", - "version": "3.62.0", + "version": "3.62.2", "description": "Sanity plugin for running/debugging GROQ-queries against Sanity datasets", "keywords": [ "sanity", diff --git a/packages/create-sanity/package.json b/packages/create-sanity/package.json index d508e678ee1..8634ffbacfb 100644 --- a/packages/create-sanity/package.json +++ b/packages/create-sanity/package.json @@ -1,6 +1,6 @@ { "name": "create-sanity", - "version": "3.62.0", + "version": "3.62.2", "description": "Initialize a new Sanity project", "keywords": [ "sanity", @@ -26,7 +26,7 @@ "index.js" ], "dependencies": { - "@sanity/cli": "3.62.0", + "@sanity/cli": "3.62.2", "resolve-pkg": "^2.0.0" }, "engines": { diff --git a/packages/groq/package.json b/packages/groq/package.json index 7579e1096a3..56f2266bcc0 100644 --- a/packages/groq/package.json +++ b/packages/groq/package.json @@ -1,6 +1,6 @@ { "name": "groq", - "version": "3.62.0", + "version": "3.62.2", "description": "Tagged template literal for Sanity.io GROQ-queries", "keywords": [ "sanity", diff --git a/packages/sanity/package.json b/packages/sanity/package.json index 1cde916993c..118007c39b0 100644 --- a/packages/sanity/package.json +++ b/packages/sanity/package.json @@ -1,6 +1,6 @@ { "name": "sanity", - "version": "3.62.0", + "version": "3.62.2", "description": "Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches", "keywords": [ "sanity", @@ -158,27 +158,27 @@ "@rexxars/react-json-inspector": "^8.0.1", "@sanity/asset-utils": "^2.0.6", "@sanity/bifur-client": "^0.4.1", - "@sanity/block-tools": "3.62.0", - "@sanity/cli": "3.62.0", + "@sanity/block-tools": "3.62.2", + "@sanity/cli": "3.62.2", "@sanity/client": "^6.22.2", "@sanity/color": "^3.0.0", - "@sanity/diff": "3.62.0", + "@sanity/diff": "3.62.2", "@sanity/diff-match-patch": "^3.1.1", "@sanity/eventsource": "^5.0.0", "@sanity/export": "^3.41.0", "@sanity/icons": "^3.4.0", "@sanity/image-url": "^1.0.2", "@sanity/import": "^3.37.3", - "@sanity/insert-menu": "1.0.9", + "@sanity/insert-menu": "1.0.10", "@sanity/logos": "^2.1.4", - "@sanity/migrate": "3.62.0", - "@sanity/mutator": "3.62.0", - "@sanity/presentation": "1.17.3", - "@sanity/schema": "3.62.0", + "@sanity/migrate": "3.62.2", + "@sanity/mutator": "3.62.2", + "@sanity/presentation": "1.17.6", + "@sanity/schema": "3.62.2", "@sanity/telemetry": "^0.7.7", - "@sanity/types": "3.62.0", + "@sanity/types": "3.62.2", "@sanity/ui": "^2.8.10", - "@sanity/util": "3.62.0", + "@sanity/util": "3.62.2", "@sanity/uuid": "^3.0.1", "@sentry/react": "^8.7.0", "@tanstack/react-table": "^8.16.0", @@ -274,7 +274,7 @@ "@repo/dev-aliases": "workspace:*", "@repo/package.config": "workspace:*", "@repo/test-config": "workspace:*", - "@sanity/codegen": "3.62.0", + "@sanity/codegen": "3.62.2", "@sanity/generate-help-url": "^3.0.0", "@sanity/pkg-utils": "6.11.4", "@sanity/tsdoc": "1.0.113", diff --git a/packages/sanity/src/_singletons/context/SanityCreateConfigContext.tsx b/packages/sanity/src/_singletons/context/SanityCreateConfigContext.tsx new file mode 100644 index 00000000000..2f89b9833fa --- /dev/null +++ b/packages/sanity/src/_singletons/context/SanityCreateConfigContext.tsx @@ -0,0 +1,13 @@ +import {createContext} from 'sanity/_createContext' + +import type {SanityCreateConfigContextValue} from '../../core' + +/** + * @internal + */ +export const SanityCreateConfigContext = createContext( + 'sanity/_singletons/context/start-in-create-enabled', + { + startInCreateEnabled: false, + }, +) diff --git a/packages/sanity/src/_singletons/index.ts b/packages/sanity/src/_singletons/index.ts index 6a6ed1c9c2e..2c0b4c89528 100644 --- a/packages/sanity/src/_singletons/index.ts +++ b/packages/sanity/src/_singletons/index.ts @@ -49,6 +49,7 @@ export * from './context/ResourceCacheContext' export * from './context/ReviewChangesContext' export * from './context/RouterContext' export * from './context/RouterHistoryContext' +export * from './context/SanityCreateConfigContext' export * from './context/ScheduledPublishingEnabledContext' export * from './context/SchedulePublishingUpsellContext' export * from './context/Schedules' diff --git a/packages/sanity/src/core/config/__tests__/resolveConfig.test.ts b/packages/sanity/src/core/config/__tests__/resolveConfig.test.ts index 7737e493488..a82286b6042 100644 --- a/packages/sanity/src/core/config/__tests__/resolveConfig.test.ts +++ b/packages/sanity/src/core/config/__tests__/resolveConfig.test.ts @@ -162,6 +162,7 @@ describe('resolveConfig', () => { {name: 'sanity/comments'}, {name: 'sanity/tasks'}, {name: 'sanity/scheduled-publishing'}, + {name: 'sanity/create-integration'}, {name: 'sanity/releases'}, ]) }) @@ -189,6 +190,7 @@ describe('resolveConfig', () => { expect(workspace.__internal.options.plugins).toMatchObject([ {name: 'sanity/comments'}, {name: 'sanity/tasks'}, + {name: 'sanity/create-integration'}, {name: 'sanity/releases'}, ]) }) diff --git a/packages/sanity/src/core/config/configPropertyReducers.ts b/packages/sanity/src/core/config/configPropertyReducers.ts index c2b6a66205a..221ad852b12 100644 --- a/packages/sanity/src/core/config/configPropertyReducers.ts +++ b/packages/sanity/src/core/config/configPropertyReducers.ts @@ -391,3 +391,48 @@ export const legacySearchEnabledReducer: ConfigPropertyReducer { + const {config, initialValue} = opts + const flattenedConfig = flattenConfig(config, []) + + const result = flattenedConfig.reduce((acc, {config: innerConfig}) => { + const resolver = innerConfig.beta?.create?.startInCreateEnabled + + if (!resolver && typeof resolver !== 'boolean') return acc + if (typeof resolver === 'boolean') return resolver + + throw new Error( + `Expected \`beta.create.startInCreateEnabled\` to be a boolean, but received ${getPrintableType( + resolver, + )}`, + ) + }, initialValue) + + return result +} + +export const createFallbackOriginReducer = (config: PluginOptions): string | undefined => { + const flattenedConfig = flattenConfig(config, []) + + const result = flattenedConfig.reduce( + (acc, {config: innerConfig}) => { + const resolver = innerConfig.beta?.create?.fallbackStudioOrigin + + if (!resolver) return acc + if (typeof resolver === 'string') return resolver + + throw new Error( + `Expected \`beta.create.fallbackStudioOrigin\` to be a string, but received ${getPrintableType( + resolver, + )}`, + ) + }, + undefined as string | undefined, + ) + + return result +} diff --git a/packages/sanity/src/core/config/create/__tests__/startInCreateSortedActions.test.ts b/packages/sanity/src/core/config/create/__tests__/startInCreateSortedActions.test.ts new file mode 100644 index 00000000000..ccc2cecc84b --- /dev/null +++ b/packages/sanity/src/core/config/create/__tests__/startInCreateSortedActions.test.ts @@ -0,0 +1,40 @@ +import {describe, expect, it} from 'vitest' + +import {type DocumentActionComponent} from '../../document/actions' +import { + getStartInCreateSortedActions, + START_IN_CREATE_ACTION_NAME, +} from '../startInCreateSortedActions' + +describe('getStartInCreateSortedActions', () => { + it(`sorts "Start in Create" action first`, async () => { + const StartInCreateAction: DocumentActionComponent = () => { + return null + } + StartInCreateAction.action = START_IN_CREATE_ACTION_NAME + + const Action1: DocumentActionComponent = () => { + return null + } + const Action2: DocumentActionComponent = () => { + return null + } + + const actions = [Action1, Action2, StartInCreateAction] + const sortedActions = getStartInCreateSortedActions(actions) + expect(sortedActions).toEqual([StartInCreateAction, Action1, Action2]) + }) + + it(`leaves actions untouched when "Start in Create" actions is not present`, async () => { + const Action1: DocumentActionComponent = () => { + return null + } + const Action2: DocumentActionComponent = () => { + return null + } + + const actions = [Action1, Action2] + const sortedActions = getStartInCreateSortedActions(actions) + expect(sortedActions).toEqual([Action1, Action2]) + }) +}) diff --git a/packages/sanity/src/core/config/create/startInCreateSortedActions.ts b/packages/sanity/src/core/config/create/startInCreateSortedActions.ts new file mode 100644 index 00000000000..0f3938722ab --- /dev/null +++ b/packages/sanity/src/core/config/create/startInCreateSortedActions.ts @@ -0,0 +1,23 @@ +//this file has to live here to avoid cyclic dependencies between config<->create +import {type DocumentActionComponent} from '../document' + +// The "Start in Create" action must be sorted first, so we need a sort key; the action string – +// we also don't want this string in the config interfaces, so we need the cheeky cast to smuggle it through +export const START_IN_CREATE_ACTION_NAME = + 'startInCreate' as unknown as DocumentActionComponent['action'] + +/** + * Sorts "Start in Create" action first, when present + */ +export function getStartInCreateSortedActions( + actions: DocumentActionComponent[], +): DocumentActionComponent[] { + return [...actions].sort((a, b) => { + if (a.action === START_IN_CREATE_ACTION_NAME) { + return -1 + } else if (b.action === START_IN_CREATE_ACTION_NAME) { + return 1 + } + return 0 + }) +} diff --git a/packages/sanity/src/core/config/prepareConfig.ts b/packages/sanity/src/core/config/prepareConfig.ts index 054250acc5b..dbc82006572 100644 --- a/packages/sanity/src/core/config/prepareConfig.ts +++ b/packages/sanity/src/core/config/prepareConfig.ts @@ -25,6 +25,7 @@ import {operatorDefinitions} from '../studio/components/navbar/search/definition import {type InitialValueTemplateItem, type Template, type TemplateItem} from '../templates' import {EMPTY_ARRAY, isNonNullable} from '../util' import { + createFallbackOriginReducer, documentActionsReducer, documentBadgesReducer, documentCommentsEnabledReducer, @@ -42,9 +43,11 @@ import { partialIndexingEnabledReducer, resolveProductionUrlReducer, schemaTemplatesReducer, + startInCreateEnabledReducer, toolsReducer, } from './configPropertyReducers' import {ConfigResolutionError} from './ConfigResolutionError' +import {getStartInCreateSortedActions} from './create/startInCreateSortedActions' import {createDefaultIcon} from './createDefaultIcon' import {documentFieldActionsReducer, initialDocumentFieldActions} from './document' import {resolveConfigProperty} from './resolveConfigProperty' @@ -495,14 +498,16 @@ function resolveSource({ config, }), document: { - actions: (partialContext) => - resolveConfigProperty({ + actions: (partialContext) => { + const actions = resolveConfigProperty({ config, context: {...context, ...partialContext}, initialValue: initialDocumentActions, propertyName: 'document.actions', reducer: documentActionsReducer, - }), + }) + return getStartInCreateSortedActions(actions) + }, badges: (partialContext) => resolveConfigProperty({ config, @@ -648,6 +653,10 @@ function resolveSource({ // This beta feature is no longer available. enabled: false, }, + create: { + startInCreateEnabled: startInCreateEnabledReducer({config, initialValue: false}), + fallbackStudioOrigin: createFallbackOriginReducer(config), + }, }, } diff --git a/packages/sanity/src/core/config/resolveDefaultPlugins.ts b/packages/sanity/src/core/config/resolveDefaultPlugins.ts index 8bd65115703..7fb2e887263 100644 --- a/packages/sanity/src/core/config/resolveDefaultPlugins.ts +++ b/packages/sanity/src/core/config/resolveDefaultPlugins.ts @@ -1,4 +1,5 @@ import {comments} from '../comments/plugin' +import {createIntegration} from '../create/createIntegrationPlugin' import {releases, RELEASES_NAME} from '../releases/plugin' import {DEFAULT_SCHEDULED_PUBLISH_PLUGIN_OPTIONS} from '../scheduledPublishing/constants' import {SCHEDULED_PUBLISHING_NAME, scheduledPublishing} from '../scheduledPublishing/plugin' @@ -10,7 +11,7 @@ import { type WorkspaceOptions, } from './types' -const defaultPlugins = [comments(), tasks(), scheduledPublishing(), releases()] +const defaultPlugins = [comments(), tasks(), scheduledPublishing(), createIntegration(), releases()] export function getDefaultPlugins( options: DefaultPluginsWorkspaceOptions, diff --git a/packages/sanity/src/core/config/types.ts b/packages/sanity/src/core/config/types.ts index fd1657dfb94..53eccdab74f 100644 --- a/packages/sanity/src/core/config/types.ts +++ b/packages/sanity/src/core/config/types.ts @@ -388,10 +388,12 @@ export interface PluginOptions { */ enableLegacySearch?: boolean } + /** Configuration for studio beta features. * @internal */ beta?: BetaFeatures + /** Configuration for error handling. * @beta */ @@ -935,4 +937,41 @@ interface BetaFeatures { */ enabled: boolean } + + /** + * @beta + */ + create?: { + /** + * When true, a "Start in Sanity Create" action will be shown for all new documents, in place of regular document actions, + * when the following are true: + * - the origin of the current url is listed under Studios in sanity.to/manage (OR fallbackStudioOrigin is provided) + * - [origin]/static/create-manifest.json is available over HTTP GET + * + * The manifest file is automatically created and deployed when deploying studios with `sanity deploy` + * + * @see #fallbackStudioOrigin + */ + startInCreateEnabled?: boolean + + /** + * To show the "Start in Create" button on localhost, or in studios not listed under Studios in sanity.io/manage + * provide a fallback origin as a string. + * + * The string must be the exactly equal `name` as shown for the Studio in manage, and the studio must have create-manifest.json available. + * + * If the provided fallback Studio does not expose create-manifest.json "Start in Sanity Create" will fail when using the fallback. + * + * Example: `wonderful.sanity.studio` + * + * Keep in mind that when fallback origin is used, Sanity Create will used the schema types and dataset in the *deployed* Studio, + * not from localhost. + * + * To see data synced from Sanity Create in your localhost Studio, you must ensure that the deployed fallback studio uses the same + * workspace and schemas as your local configuration. + * + * @see #startInCreateEnabled + */ + fallbackStudioOrigin?: string + } } diff --git a/packages/sanity/src/core/create/__telemetry__/create.telemetry.ts b/packages/sanity/src/core/create/__telemetry__/create.telemetry.ts new file mode 100644 index 00000000000..d950e671b59 --- /dev/null +++ b/packages/sanity/src/core/create/__telemetry__/create.telemetry.ts @@ -0,0 +1,32 @@ +import {defineEvent} from '@sanity/telemetry' + +export const CreateDocumentLinkCtaClicked = defineEvent({ + name: 'Create Document Link CTA Clicked', + version: 1, + description: 'The "Start in Sanity Create" button is clicked.', +}) + +export const CreateDocumentLinkAccepted = defineEvent({ + name: 'Create Document Link Accepted', + version: 1, + description: + 'Continue in the "Start in Sanity Create" dialog was pressed, or auto-confirm was enabled.', +}) + +export const CreateDocumentUnlinkCtaClicked = defineEvent({ + name: 'Create Document Unlink CTA Clicked', + version: 1, + description: 'The Unlink action was clicked', +}) + +export const CreateDocumentUnlinkApproved = defineEvent({ + name: 'Create Document Unlink Approved', + version: 1, + description: 'User confirmed that they want the Studio document unlinked', +}) + +export const CreateDocumentOpened = defineEvent({ + name: 'Create Document Opened', + version: 1, + description: 'User clicked "Edit in Create"', +}) diff --git a/packages/sanity/src/core/create/__tests__/createDocumentUrl.test.ts b/packages/sanity/src/core/create/__tests__/createDocumentUrl.test.ts new file mode 100644 index 00000000000..4fffbf93f88 --- /dev/null +++ b/packages/sanity/src/core/create/__tests__/createDocumentUrl.test.ts @@ -0,0 +1,70 @@ +import {describe, expect, it} from 'vitest' + +import {getCreateDocumentUrl, getCreateLinkUrl} from '../createDocumentUrls' +import {type CreateLinkMetadata} from '../types' + +describe('createDocumentUrls', () => { + describe('getCreateDocumentUrl', () => { + it(`returns Create prod document url`, async () => { + const metadata: CreateLinkMetadata = { + _id: 'id', + dataset: 'dataset', + ejected: false, + } + expect(getCreateDocumentUrl(metadata)).toEqual('https://www.sanity.io/app/create/dataset/id') + }) + + it(`returns Create staging document url`, async () => { + const metadata: CreateLinkMetadata = { + _id: 'id', + dataset: 'dataset', + ejected: false, + host: 'https://www.sanity.work', + } + expect(getCreateDocumentUrl(metadata)).toEqual( + 'https://create-staging.sanity.build/app/create/dataset/id', + ) + }) + }) + + describe('getCreateLinkUrl', () => { + it(`returns Create prod create link url`, async () => { + expect( + getCreateLinkUrl({ + docId: 'id', + documentType: 'documentType', + appId: 'appId', + projectId: 'projectId', + workspaceName: 'workspace', + }), + ).toEqual( + 'https://www.sanity.io/app/create/studio-import?' + + 'projectId=projectId&' + + 'applicationId=appId&' + + 'workspaceName=workspace&' + + 'documentType=documentType&' + + 'documentId=id', + ) + }) + + it(`returns Create staging create link url`, async () => { + expect( + getCreateLinkUrl({ + docId: 'id', + documentType: 'documentType', + appId: 'appId', + projectId: 'projectId', + workspaceName: 'workspace', + customHost: 'https://www.sanity.work', + }), + ).toEqual( + 'https://create-staging.sanity.build/app/create/studio-import?' + + 'projectId=projectId&' + + 'applicationId=appId&' + + 'workspaceName=workspace&' + + 'documentType=documentType&' + + 'documentId=id', + ) + }) + }) +}) diff --git a/packages/sanity/src/core/create/components/CreateIntegrationWrapper.tsx b/packages/sanity/src/core/create/components/CreateIntegrationWrapper.tsx new file mode 100644 index 00000000000..9d6fb12b88e --- /dev/null +++ b/packages/sanity/src/core/create/components/CreateIntegrationWrapper.tsx @@ -0,0 +1,6 @@ +import {type LayoutProps} from '../../config' +import {SanityCreateConfigProvider} from '../context/SanityCreateConfigProvider' + +export function CreateIntegrationWrapper(props: LayoutProps) { + return {props.renderDefault(props)} +} diff --git a/packages/sanity/src/core/create/components/CreateLearnMoreButton.tsx b/packages/sanity/src/core/create/components/CreateLearnMoreButton.tsx new file mode 100644 index 00000000000..c01110e95ae --- /dev/null +++ b/packages/sanity/src/core/create/components/CreateLearnMoreButton.tsx @@ -0,0 +1,25 @@ +import {LaunchIcon} from '@sanity/icons' +import {type ForwardedRef, forwardRef} from 'react' + +import {Button} from '../../../ui-components' +import {useTranslation} from '../../i18n' +import {createLocaleNamespace} from '../i18n' + +export const CreateLearnMoreButton = forwardRef(function CreateLearnMoreButton( + props, + ref: ForwardedRef, +) { + const {t} = useTranslation(createLocaleNamespace) + return ( + + {troubleshootingOpen && ( + + + {t('linking-in-progress-dialog.troubleshooting.content')} + + + )} + + + + + ) +} diff --git a/packages/sanity/src/core/create/start-in-create/StartInCreateAction.tsx b/packages/sanity/src/core/create/start-in-create/StartInCreateAction.tsx new file mode 100644 index 00000000000..d4f0275be97 --- /dev/null +++ b/packages/sanity/src/core/create/start-in-create/StartInCreateAction.tsx @@ -0,0 +1,92 @@ +import {useCallback, useState} from 'react' + +import { + type DocumentActionComponent, + type DocumentActionDescription, + type DocumentActionProps, +} from '../../config' +import {START_IN_CREATE_ACTION_NAME} from '../../config/create/startInCreateSortedActions' +import {useTranslation} from '../../i18n' +import {useSchemaType} from '../../scheduledPublishing/hooks/useSchemaType' +import {isStartInCreateAutoConfirmed, setStartInCreateAutoConfirm} from '../createStorage' +import {isSanityCreateExcludedType} from '../createUtils' +import {createLocaleNamespace} from '../i18n' +import {type AppIdCache} from '../studio-app/appIdCache' +import {useStudioAppIdStore} from '../studio-app/useStudioAppIdStore' +import {type CreateLinkedSanityDocument} from '../types' +import {useSanityCreateTelemetry} from '../useSanityCreateTelemetry' +import {CreateLinkingDialog} from './CreateLinkingDialog' +import {StartInCreateDialog} from './StartInCreateDialog' + +export function createStartInCreateAction(appIdCache: AppIdCache): DocumentActionComponent { + const StartInCreateActionWrapper: DocumentActionComponent = function StartInCreateActionWrapper( + props: DocumentActionProps, + ): DocumentActionDescription | null { + return StartInCreateAction({appIdCache, ...props}) + } + + StartInCreateActionWrapper.action = START_IN_CREATE_ACTION_NAME + return StartInCreateActionWrapper +} + +export function StartInCreateAction( + props: DocumentActionProps & {appIdCache: AppIdCache}, +): DocumentActionDescription | null { + const {id, type, draft, liveEdit, published, appIdCache} = props + const doc = (draft ?? published) as CreateLinkedSanityDocument + + const {appId} = useStudioAppIdStore(appIdCache) + const {t} = useTranslation(createLocaleNamespace) + const schemaType = useSchemaType(type) + const telemetry = useSanityCreateTelemetry() + + const [isDialogOpen, setDialogOpen] = useState(false) + const [isLinking, setLinking] = useState(false) + const [autoConfirm, setAutoConfirm] = useState(() => isStartInCreateAutoConfirmed()) + const closeDialog = useCallback(() => setDialogOpen(false), []) + + const linkingStarted = useCallback((dontShowAgain: boolean) => { + setStartInCreateAutoConfirm(dontShowAgain) + setAutoConfirm(dontShowAgain) + setLinking(true) + }, []) + + const isExcludedByOption = schemaType && isSanityCreateExcludedType(schemaType) + const createLinkId = (draft?._id ?? published?._id ?? liveEdit) ? id : `drafts.${id}` + + //appId will always be undefined when start in create is disabled via config + if (isExcludedByOption || !appId || doc?._createdAt) { + return null + } + + return { + label: t('start-in-create-action.label'), + dialog: isLinking + ? { + type: 'custom', + component: , + } + : isDialogOpen && { + type: 'dialog', + onClose: closeDialog, + header: t('start-in-create-dialog.header'), + width: 'small', + content: ( + + ), + }, + onHandle: () => { + if (!isDialogOpen) { + telemetry.linkCtaClicked() + } + setDialogOpen(true) + }, + tone: 'default', + } +} diff --git a/packages/sanity/src/core/create/start-in-create/StartInCreateDialog.tsx b/packages/sanity/src/core/create/start-in-create/StartInCreateDialog.tsx new file mode 100644 index 00000000000..962f4b4a5d1 --- /dev/null +++ b/packages/sanity/src/core/create/start-in-create/StartInCreateDialog.tsx @@ -0,0 +1,88 @@ +import {LaunchIcon} from '@sanity/icons' +import {Checkbox, Flex, Stack, Text, useToast} from '@sanity/ui' +import {useCallback, useEffect, useId, useState} from 'react' + +import {Button} from '../../../ui-components' +import {useTranslation} from '../../i18n' +import {useWorkspace} from '../../studio' +import {CreateLearnMoreButton} from '../components/CreateLearnMoreButton' +import {CreateSvg} from '../components/media/CreateSvg' +import {getCreateLinkUrl} from '../createDocumentUrls' +import {createLocaleNamespace} from '../i18n' +import {useSanityCreateTelemetry} from '../useSanityCreateTelemetry' + +export interface StartInCreateDialogProps { + createLinkId: string + appId: string + type: string + onLinkingStarted: (autoConfirm: boolean) => void + autoConfirm: boolean +} + +export function StartInCreateDialog(props: StartInCreateDialogProps) { + const {createLinkId, appId, type, onLinkingStarted, autoConfirm} = props + const {t} = useTranslation(createLocaleNamespace) + const checkboxId = useId() + const [dontShowAgain, setDontShowAgain] = useState(false) + + const telemetry = useSanityCreateTelemetry() + const toggleDontShowAgain = useCallback(() => setDontShowAgain((current) => !current), []) + + const {push: pushToast} = useToast() + const workspace = useWorkspace() + + const createUrl = getCreateLinkUrl({ + projectId: workspace.projectId, + appId, + workspaceName: workspace.name, + documentType: type, + docId: createLinkId, + }) + + const startLinking = useCallback(() => { + if (!createUrl) { + pushToast({ + title: t('start-in-create-dialog.error-toast.unresolved-url'), + status: 'warning', + }) + return + } + + window?.open(createUrl, '_blank')?.focus() + onLinkingStarted(autoConfirm || dontShowAgain) + telemetry.linkAccepted() + }, [createUrl, onLinkingStarted, pushToast, t, dontShowAgain, autoConfirm, telemetry]) + + useEffect(() => { + if (autoConfirm && createUrl) { + startLinking() + } + }, [autoConfirm, startLinking, createUrl]) + + return ( + + + + {t('start-in-create-dialog.lede')} + + + {t('start-in-create-dialog.details')} + + + + + {t('start-in-create-dialog.dont-remind-me-checkbox')} + + + + +