@sanity/cli 6.1.0 → 6.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -78
- package/dist/actions/init/processTemplate.js +27 -49
- package/dist/actions/init/processTemplate.js.map +1 -1
- package/dist/actions/init/sdkAppDependencies.js +3 -3
- package/dist/actions/init/sdkAppDependencies.js.map +1 -1
- package/dist/actions/init/studioDependencies.js +3 -3
- package/dist/actions/init/studioDependencies.js.map +1 -1
- package/dist/actions/manifest/__tests__/resolveSchemaIcon.test.js +157 -0
- package/dist/actions/manifest/__tests__/resolveSchemaIcon.test.js.map +1 -0
- package/dist/actions/manifest/extractWorkspaceManifest.js +12 -5
- package/dist/actions/manifest/extractWorkspaceManifest.js.map +1 -1
- package/dist/actions/manifest/iconResolver.js +13 -6
- package/dist/actions/manifest/iconResolver.js.map +1 -1
- package/dist/actions/manifest/resolveSchemaIcon.js +43 -0
- package/dist/actions/manifest/resolveSchemaIcon.js.map +1 -0
- package/dist/actions/schema/uploadSchemaToLexicon.js +6 -4
- package/dist/actions/schema/uploadSchemaToLexicon.js.map +1 -1
- package/dist/commands/init.js +22 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/hooks/prerun/injectEnvVariables.js +2 -1
- package/dist/hooks/prerun/injectEnvVariables.js.map +1 -1
- package/oclif.config.js +1 -1
- package/oclif.manifest.json +374 -374
- package/package.json +5 -8
- package/dist/actions/manifest/SchemaIcon.js +0 -23
- package/dist/actions/manifest/SchemaIcon.js.map +0 -1
|
@@ -1,56 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export function processTemplate(options) {
|
|
1
|
+
/**
|
|
2
|
+
* Process a template string by replacing placeholder variables with actual values.
|
|
3
|
+
*
|
|
4
|
+
* String variables use the format `'%variableName%'` (inside string literals).
|
|
5
|
+
* Boolean variables use the format `__BOOL__variableName__` (as bare identifiers).
|
|
6
|
+
*/ export function processTemplate(options) {
|
|
7
7
|
const { includeBooleanTransform = false, template, variables } = options;
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Replace string placeholders: '%variableName%' or "%variableName%"
|
|
9
|
+
let result = template.trimStart().replaceAll(/(['"])%([\w]+)%\1/g, (_match, quote, variableName)=>{
|
|
10
|
+
if (!(variableName in variables)) {
|
|
11
|
+
throw new Error(`Template variable '%${variableName}%' not defined`);
|
|
12
|
+
}
|
|
13
|
+
const newValue = typeof variables[variableName] === 'string' ? variables[variableName] : '';
|
|
14
|
+
// Escape backslashes first, then the surrounding quote character
|
|
15
|
+
const escaped = newValue.replaceAll('\\', '\\\\').replaceAll(quote, `\\${quote}`);
|
|
16
|
+
return `${quote}${escaped}${quote}`;
|
|
10
17
|
});
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const variableName = value.slice(1, -1);
|
|
19
|
-
if (!(variableName in variables)) {
|
|
20
|
-
throw new Error(`Template variable '${value}' not defined`);
|
|
21
|
-
}
|
|
22
|
-
const newValue = variables[variableName];
|
|
23
|
-
/*
|
|
24
|
-
* although there are valid non-strings in our config,
|
|
25
|
-
* they're not in StringLiteral nodes, so assume undefined
|
|
26
|
-
*/ node.value = typeof newValue === 'string' ? newValue : '';
|
|
18
|
+
// Replace boolean placeholders: __BOOL__variableName__
|
|
19
|
+
if (includeBooleanTransform) {
|
|
20
|
+
result = result.replaceAll(/__BOOL__(\w+)__/g, (_match, variableName)=>{
|
|
21
|
+
if (!(variableName in variables)) {
|
|
22
|
+
throw new Error(`Template variable '${variableName}' not defined`);
|
|
27
23
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
enter (path) {
|
|
32
|
-
if (!path.node.name.startsWith('__BOOL__')) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const variableName = path.node.name.replace(/^__BOOL__(.+?)__$/, '$1');
|
|
36
|
-
if (!(variableName in variables)) {
|
|
37
|
-
throw new Error(`Template variable '${variableName.toString()}' not defined`);
|
|
38
|
-
}
|
|
39
|
-
const value = variables[variableName];
|
|
40
|
-
if (typeof value !== 'boolean') {
|
|
41
|
-
throw new TypeError(`Expected boolean value for '${variableName.toString()}'`);
|
|
42
|
-
}
|
|
43
|
-
path.replaceWith({
|
|
44
|
-
type: 'BooleanLiteral',
|
|
45
|
-
value
|
|
46
|
-
});
|
|
47
|
-
}
|
|
24
|
+
const value = variables[variableName];
|
|
25
|
+
if (typeof value !== 'boolean') {
|
|
26
|
+
throw new TypeError(`Expected boolean value for '${variableName}'`);
|
|
48
27
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}).code;
|
|
28
|
+
return String(value);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
54
32
|
}
|
|
55
33
|
|
|
56
34
|
//# sourceMappingURL=processTemplate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/init/processTemplate.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/init/processTemplate.ts"],"sourcesContent":["interface TemplateOptions<T> {\n template: string\n variables: T\n\n includeBooleanTransform?: boolean\n}\n\n/**\n * Process a template string by replacing placeholder variables with actual values.\n *\n * String variables use the format `'%variableName%'` (inside string literals).\n * Boolean variables use the format `__BOOL__variableName__` (as bare identifiers).\n */\nexport function processTemplate<T extends object>(options: TemplateOptions<T>): string {\n const {includeBooleanTransform = false, template, variables} = options\n\n // Replace string placeholders: '%variableName%' or \"%variableName%\"\n let result = template\n .trimStart()\n .replaceAll(/(['\"])%([\\w]+)%\\1/g, (_match, quote: string, variableName: string) => {\n if (!(variableName in variables)) {\n throw new Error(`Template variable '%${variableName}%' not defined`)\n }\n const newValue =\n typeof variables[variableName as keyof T] === 'string'\n ? variables[variableName as keyof T]\n : ''\n // Escape backslashes first, then the surrounding quote character\n const escaped = (newValue as string).replaceAll('\\\\', '\\\\\\\\').replaceAll(quote, `\\\\${quote}`)\n return `${quote}${escaped}${quote}`\n })\n\n // Replace boolean placeholders: __BOOL__variableName__\n if (includeBooleanTransform) {\n result = result.replaceAll(/__BOOL__(\\w+)__/g, (_match, variableName: string) => {\n if (!(variableName in variables)) {\n throw new Error(`Template variable '${variableName}' not defined`)\n }\n const value = variables[variableName as keyof T]\n if (typeof value !== 'boolean') {\n throw new TypeError(`Expected boolean value for '${variableName}'`)\n }\n return String(value)\n })\n }\n\n return result\n}\n"],"names":["processTemplate","options","includeBooleanTransform","template","variables","result","trimStart","replaceAll","_match","quote","variableName","Error","newValue","escaped","value","TypeError","String"],"mappings":"AAOA;;;;;CAKC,GACD,OAAO,SAASA,gBAAkCC,OAA2B;IAC3E,MAAM,EAACC,0BAA0B,KAAK,EAAEC,QAAQ,EAAEC,SAAS,EAAC,GAAGH;IAE/D,oEAAoE;IACpE,IAAII,SAASF,SACVG,SAAS,GACTC,UAAU,CAAC,sBAAsB,CAACC,QAAQC,OAAeC;QACxD,IAAI,CAAEA,CAAAA,gBAAgBN,SAAQ,GAAI;YAChC,MAAM,IAAIO,MAAM,CAAC,oBAAoB,EAAED,aAAa,cAAc,CAAC;QACrE;QACA,MAAME,WACJ,OAAOR,SAAS,CAACM,aAAwB,KAAK,WAC1CN,SAAS,CAACM,aAAwB,GAClC;QACN,iEAAiE;QACjE,MAAMG,UAAU,AAACD,SAAoBL,UAAU,CAAC,MAAM,QAAQA,UAAU,CAACE,OAAO,CAAC,EAAE,EAAEA,OAAO;QAC5F,OAAO,GAAGA,QAAQI,UAAUJ,OAAO;IACrC;IAEF,uDAAuD;IACvD,IAAIP,yBAAyB;QAC3BG,SAASA,OAAOE,UAAU,CAAC,oBAAoB,CAACC,QAAQE;YACtD,IAAI,CAAEA,CAAAA,gBAAgBN,SAAQ,GAAI;gBAChC,MAAM,IAAIO,MAAM,CAAC,mBAAmB,EAAED,aAAa,aAAa,CAAC;YACnE;YACA,MAAMI,QAAQV,SAAS,CAACM,aAAwB;YAChD,IAAI,OAAOI,UAAU,WAAW;gBAC9B,MAAM,IAAIC,UAAU,CAAC,4BAA4B,EAAEL,aAAa,CAAC,CAAC;YACpE;YACA,OAAOM,OAAOF;QAChB;IACF;IAEA,OAAOT;AACT"}
|
|
@@ -3,12 +3,12 @@ export const sdkAppDependencies = {
|
|
|
3
3
|
// change these to 'latest' as in studioDependencies.ts once SDK v3 is released
|
|
4
4
|
'@sanity/sdk': '^2',
|
|
5
5
|
'@sanity/sdk-react': '^2',
|
|
6
|
-
react: '^19.2',
|
|
7
|
-
'react-dom': '^19.2'
|
|
6
|
+
react: '^19.2.4',
|
|
7
|
+
'react-dom': '^19.2.4'
|
|
8
8
|
},
|
|
9
9
|
devDependencies: {
|
|
10
10
|
'@sanity/eslint-config-studio': 'latest',
|
|
11
|
-
'@types/react': '^19.
|
|
11
|
+
'@types/react': '^19.2.14',
|
|
12
12
|
eslint: '^9.28',
|
|
13
13
|
prettier: '^3.5',
|
|
14
14
|
sanity: 'latest',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/init/sdkAppDependencies.ts"],"sourcesContent":["export const sdkAppDependencies = {\n dependencies: {\n // change these to 'latest' as in studioDependencies.ts once SDK v3 is released\n '@sanity/sdk': '^2',\n '@sanity/sdk-react': '^2',\n react: '^19.2',\n 'react-dom': '^19.2',\n },\n\n devDependencies: {\n '@sanity/eslint-config-studio': 'latest',\n '@types/react': '^19.
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/init/sdkAppDependencies.ts"],"sourcesContent":["export const sdkAppDependencies = {\n dependencies: {\n // change these to 'latest' as in studioDependencies.ts once SDK v3 is released\n '@sanity/sdk': '^2',\n '@sanity/sdk-react': '^2',\n react: '^19.2.4',\n 'react-dom': '^19.2.4',\n },\n\n devDependencies: {\n '@sanity/eslint-config-studio': 'latest',\n '@types/react': '^19.2.14',\n eslint: '^9.28',\n prettier: '^3.5',\n sanity: 'latest',\n typescript: '^5.8', // Peer dependency of eslint-config-studio (implicitly)\n },\n}\n"],"names":["sdkAppDependencies","dependencies","react","devDependencies","eslint","prettier","sanity","typescript"],"mappings":"AAAA,OAAO,MAAMA,qBAAqB;IAChCC,cAAc;QACZ,+EAA+E;QAC/E,eAAe;QACf,qBAAqB;QACrBC,OAAO;QACP,aAAa;IACf;IAEAC,iBAAiB;QACf,gCAAgC;QAChC,gBAAgB;QAChBC,QAAQ;QACRC,UAAU;QACVC,QAAQ;QACRC,YAAY;IACd;AACF,EAAC"}
|
|
@@ -6,15 +6,15 @@ export const studioDependencies = {
|
|
|
6
6
|
// Official studio plugin dependencies
|
|
7
7
|
'@sanity/vision': 'latest',
|
|
8
8
|
// Non-Sanity dependencies
|
|
9
|
-
react: '^19.
|
|
10
|
-
'react-dom': '^19.
|
|
9
|
+
react: '^19.2.4',
|
|
10
|
+
'react-dom': '^19.2.4',
|
|
11
11
|
'styled-components': '^6.1.18'
|
|
12
12
|
},
|
|
13
13
|
devDependencies: {
|
|
14
14
|
// Linting/tooling
|
|
15
15
|
'@sanity/eslint-config-studio': 'latest',
|
|
16
16
|
// When using typescript, we'll want the these types too, so might as well install them
|
|
17
|
-
'@types/react': '^19.
|
|
17
|
+
'@types/react': '^19.2.14',
|
|
18
18
|
eslint: '^9.28',
|
|
19
19
|
prettier: '^3.5',
|
|
20
20
|
typescript: '^5.8'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/init/studioDependencies.ts"],"sourcesContent":["export const studioDependencies = {\n // Dependencies for a default Sanity installation\n dependencies: {\n // Official studio dependencies\n sanity: 'latest',\n\n // Official studio plugin dependencies\n '@sanity/vision': 'latest',\n\n // Non-Sanity dependencies\n react: '^19.
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/init/studioDependencies.ts"],"sourcesContent":["export const studioDependencies = {\n // Dependencies for a default Sanity installation\n dependencies: {\n // Official studio dependencies\n sanity: 'latest',\n\n // Official studio plugin dependencies\n '@sanity/vision': 'latest',\n\n // Non-Sanity dependencies\n react: '^19.2.4',\n 'react-dom': '^19.2.4',\n 'styled-components': '^6.1.18',\n },\n\n devDependencies: {\n // Linting/tooling\n '@sanity/eslint-config-studio': 'latest',\n // When using typescript, we'll want the these types too, so might as well install them\n '@types/react': '^19.2.14',\n eslint: '^9.28',\n prettier: '^3.5',\n typescript: '^5.8', // Peer dependency of eslint-config-studio (implicitly)\n },\n}\n"],"names":["studioDependencies","dependencies","sanity","react","devDependencies","eslint","prettier","typescript"],"mappings":"AAAA,OAAO,MAAMA,qBAAqB;IAChC,iDAAiD;IACjDC,cAAc;QACZ,+BAA+B;QAC/BC,QAAQ;QAER,sCAAsC;QACtC,kBAAkB;QAElB,0BAA0B;QAC1BC,OAAO;QACP,aAAa;QACb,qBAAqB;IACvB;IAEAC,iBAAiB;QACf,kBAAkB;QAClB,gCAAgC;QAChC,uFAAuF;QACvF,gBAAgB;QAChBC,QAAQ;QACRC,UAAU;QACVC,YAAY;IACd;AACF,EAAC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createElement } from 'react';
|
|
3
|
+
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
4
|
+
import { resolveSchemaIcon } from '../resolveSchemaIcon.js';
|
|
5
|
+
const mockResolveLocalPackage = vi.hoisted(()=>vi.fn());
|
|
6
|
+
const mockResolveLocalPackageFrom = vi.hoisted(()=>vi.fn());
|
|
7
|
+
const mockResolveLocalPackagePath = vi.hoisted(()=>vi.fn());
|
|
8
|
+
vi.mock('@sanity/cli-core', async (importOriginal)=>{
|
|
9
|
+
const actual = await importOriginal();
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
resolveLocalPackage: mockResolveLocalPackage,
|
|
13
|
+
resolveLocalPackageFrom: mockResolveLocalPackageFrom,
|
|
14
|
+
resolveLocalPackagePath: mockResolveLocalPackagePath
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
const MockThemeProvider = ({ children })=>/*#__PURE__*/ _jsx(_Fragment, {
|
|
18
|
+
children: children
|
|
19
|
+
});
|
|
20
|
+
function setupMocks() {
|
|
21
|
+
const mockTheme = {
|
|
22
|
+
color: 'mock-theme'
|
|
23
|
+
};
|
|
24
|
+
const buildTheme = vi.fn().mockReturnValue(mockTheme);
|
|
25
|
+
const createDefaultIcon = vi.fn().mockReturnValue(/*#__PURE__*/ createElement('span', null, 'default'));
|
|
26
|
+
const fakeSanityUrl = new URL('file:///studio/project/node_modules/sanity/dist/index.js');
|
|
27
|
+
mockResolveLocalPackagePath.mockImplementation((pkg)=>{
|
|
28
|
+
if (pkg === 'sanity') return fakeSanityUrl;
|
|
29
|
+
throw new Error(`Unexpected resolveLocalPackagePath call: ${pkg}`);
|
|
30
|
+
});
|
|
31
|
+
mockResolveLocalPackageFrom.mockImplementation(async (pkg, parentUrl)=>{
|
|
32
|
+
if (parentUrl !== fakeSanityUrl) {
|
|
33
|
+
throw new Error(`Unexpected parent URL: ${parentUrl.href}`);
|
|
34
|
+
}
|
|
35
|
+
switch(pkg){
|
|
36
|
+
case '@sanity/ui':
|
|
37
|
+
{
|
|
38
|
+
return {
|
|
39
|
+
ThemeProvider: MockThemeProvider
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
case '@sanity/ui/theme':
|
|
43
|
+
{
|
|
44
|
+
return {
|
|
45
|
+
buildTheme
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
{
|
|
50
|
+
throw new Error(`Unexpected resolveLocalPackageFrom call: ${pkg}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
mockResolveLocalPackage.mockImplementation(async (pkg)=>{
|
|
55
|
+
switch(pkg){
|
|
56
|
+
case 'sanity':
|
|
57
|
+
{
|
|
58
|
+
return {
|
|
59
|
+
createDefaultIcon
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
default:
|
|
63
|
+
{
|
|
64
|
+
throw new Error(`Unexpected resolveLocalPackage call: ${pkg}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
buildTheme,
|
|
70
|
+
createDefaultIcon,
|
|
71
|
+
fakeSanityUrl,
|
|
72
|
+
mockTheme
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
describe('resolveSchemaIcon', ()=>{
|
|
76
|
+
const workDir = '/studio/project';
|
|
77
|
+
afterEach(()=>{
|
|
78
|
+
vi.clearAllMocks();
|
|
79
|
+
});
|
|
80
|
+
test('resolves @sanity/ui via sanity package location', async ()=>{
|
|
81
|
+
const { fakeSanityUrl } = setupMocks();
|
|
82
|
+
await resolveSchemaIcon({
|
|
83
|
+
title: 'Test',
|
|
84
|
+
workDir
|
|
85
|
+
});
|
|
86
|
+
expect(mockResolveLocalPackagePath).toHaveBeenCalledWith('sanity', workDir);
|
|
87
|
+
expect(mockResolveLocalPackageFrom).toHaveBeenCalledWith('@sanity/ui', fakeSanityUrl);
|
|
88
|
+
});
|
|
89
|
+
test('resolves @sanity/ui/theme via sanity package location', async ()=>{
|
|
90
|
+
const { fakeSanityUrl } = setupMocks();
|
|
91
|
+
await resolveSchemaIcon({
|
|
92
|
+
title: 'Test',
|
|
93
|
+
workDir
|
|
94
|
+
});
|
|
95
|
+
expect(mockResolveLocalPackageFrom).toHaveBeenCalledWith('@sanity/ui/theme', fakeSanityUrl);
|
|
96
|
+
});
|
|
97
|
+
test('resolves sanity from the studio workDir when no icon is provided', async ()=>{
|
|
98
|
+
setupMocks();
|
|
99
|
+
await resolveSchemaIcon({
|
|
100
|
+
title: 'Test',
|
|
101
|
+
workDir
|
|
102
|
+
});
|
|
103
|
+
expect(mockResolveLocalPackage).toHaveBeenCalledWith('sanity', workDir);
|
|
104
|
+
});
|
|
105
|
+
test('does not resolve sanity package when a valid component icon is provided', async ()=>{
|
|
106
|
+
setupMocks();
|
|
107
|
+
const CustomIcon = ()=>/*#__PURE__*/ _jsx("span", {
|
|
108
|
+
children: "custom"
|
|
109
|
+
});
|
|
110
|
+
await resolveSchemaIcon({
|
|
111
|
+
icon: CustomIcon,
|
|
112
|
+
title: 'Test',
|
|
113
|
+
workDir
|
|
114
|
+
});
|
|
115
|
+
expect(mockResolveLocalPackage).not.toHaveBeenCalledWith('sanity', workDir);
|
|
116
|
+
});
|
|
117
|
+
test('does not resolve sanity package when a React element icon is provided', async ()=>{
|
|
118
|
+
setupMocks();
|
|
119
|
+
const elementIcon = /*#__PURE__*/ createElement('svg', null, /*#__PURE__*/ createElement('path', {
|
|
120
|
+
d: 'M0 0'
|
|
121
|
+
}));
|
|
122
|
+
await resolveSchemaIcon({
|
|
123
|
+
icon: elementIcon,
|
|
124
|
+
title: 'Test',
|
|
125
|
+
workDir
|
|
126
|
+
});
|
|
127
|
+
expect(mockResolveLocalPackage).not.toHaveBeenCalledWith('sanity', workDir);
|
|
128
|
+
});
|
|
129
|
+
test('wraps output with the resolved ThemeProvider and built theme', async ()=>{
|
|
130
|
+
const { mockTheme } = setupMocks();
|
|
131
|
+
const result = await resolveSchemaIcon({
|
|
132
|
+
title: 'Test',
|
|
133
|
+
workDir
|
|
134
|
+
});
|
|
135
|
+
expect(result.type).toBe(MockThemeProvider);
|
|
136
|
+
expect(result.props).toHaveProperty('theme', mockTheme);
|
|
137
|
+
});
|
|
138
|
+
test('calls createDefaultIcon with title and subtitle when no icon is provided', async ()=>{
|
|
139
|
+
const { createDefaultIcon } = setupMocks();
|
|
140
|
+
await resolveSchemaIcon({
|
|
141
|
+
subtitle: 'document',
|
|
142
|
+
title: 'My Type',
|
|
143
|
+
workDir
|
|
144
|
+
});
|
|
145
|
+
expect(createDefaultIcon).toHaveBeenCalledWith('My Type', 'document');
|
|
146
|
+
});
|
|
147
|
+
test('passes empty string as subtitle default to createDefaultIcon', async ()=>{
|
|
148
|
+
const { createDefaultIcon } = setupMocks();
|
|
149
|
+
await resolveSchemaIcon({
|
|
150
|
+
title: 'My Type',
|
|
151
|
+
workDir
|
|
152
|
+
});
|
|
153
|
+
expect(createDefaultIcon).toHaveBeenCalledWith('My Type', '');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=resolveSchemaIcon.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/actions/manifest/__tests__/resolveSchemaIcon.test.tsx"],"sourcesContent":["import {createElement, type ReactNode} from 'react'\nimport {afterEach, describe, expect, test, vi} from 'vitest'\n\nimport {resolveSchemaIcon} from '../resolveSchemaIcon.js'\n\nconst mockResolveLocalPackage = vi.hoisted(() => vi.fn())\nconst mockResolveLocalPackageFrom = vi.hoisted(() => vi.fn())\nconst mockResolveLocalPackagePath = vi.hoisted(() => vi.fn())\n\nvi.mock('@sanity/cli-core', async (importOriginal) => {\n const actual = await importOriginal<typeof import('@sanity/cli-core')>()\n return {\n ...actual,\n resolveLocalPackage: mockResolveLocalPackage,\n resolveLocalPackageFrom: mockResolveLocalPackageFrom,\n resolveLocalPackagePath: mockResolveLocalPackagePath,\n }\n})\n\nconst MockThemeProvider = ({children}: {children: ReactNode}) => <>{children}</>\n\nfunction setupMocks() {\n const mockTheme = {color: 'mock-theme'}\n const buildTheme = vi.fn().mockReturnValue(mockTheme)\n const createDefaultIcon = vi.fn().mockReturnValue(createElement('span', null, 'default'))\n const fakeSanityUrl = new URL('file:///studio/project/node_modules/sanity/dist/index.js')\n\n mockResolveLocalPackagePath.mockImplementation((pkg: string) => {\n if (pkg === 'sanity') return fakeSanityUrl\n throw new Error(`Unexpected resolveLocalPackagePath call: ${pkg}`)\n })\n\n mockResolveLocalPackageFrom.mockImplementation(async (pkg: string, parentUrl: URL) => {\n if (parentUrl !== fakeSanityUrl) {\n throw new Error(`Unexpected parent URL: ${parentUrl.href}`)\n }\n switch (pkg) {\n case '@sanity/ui': {\n return {ThemeProvider: MockThemeProvider}\n }\n case '@sanity/ui/theme': {\n return {buildTheme}\n }\n default: {\n throw new Error(`Unexpected resolveLocalPackageFrom call: ${pkg}`)\n }\n }\n })\n\n mockResolveLocalPackage.mockImplementation(async (pkg: string) => {\n switch (pkg) {\n case 'sanity': {\n return {createDefaultIcon}\n }\n default: {\n throw new Error(`Unexpected resolveLocalPackage call: ${pkg}`)\n }\n }\n })\n\n return {buildTheme, createDefaultIcon, fakeSanityUrl, mockTheme}\n}\n\ndescribe('resolveSchemaIcon', () => {\n const workDir = '/studio/project'\n\n afterEach(() => {\n vi.clearAllMocks()\n })\n\n test('resolves @sanity/ui via sanity package location', async () => {\n const {fakeSanityUrl} = setupMocks()\n\n await resolveSchemaIcon({title: 'Test', workDir})\n\n expect(mockResolveLocalPackagePath).toHaveBeenCalledWith('sanity', workDir)\n expect(mockResolveLocalPackageFrom).toHaveBeenCalledWith('@sanity/ui', fakeSanityUrl)\n })\n\n test('resolves @sanity/ui/theme via sanity package location', async () => {\n const {fakeSanityUrl} = setupMocks()\n\n await resolveSchemaIcon({title: 'Test', workDir})\n\n expect(mockResolveLocalPackageFrom).toHaveBeenCalledWith('@sanity/ui/theme', fakeSanityUrl)\n })\n\n test('resolves sanity from the studio workDir when no icon is provided', async () => {\n setupMocks()\n\n await resolveSchemaIcon({title: 'Test', workDir})\n\n expect(mockResolveLocalPackage).toHaveBeenCalledWith('sanity', workDir)\n })\n\n test('does not resolve sanity package when a valid component icon is provided', async () => {\n setupMocks()\n const CustomIcon = () => <span>custom</span>\n\n await resolveSchemaIcon({icon: CustomIcon, title: 'Test', workDir})\n\n expect(mockResolveLocalPackage).not.toHaveBeenCalledWith('sanity', workDir)\n })\n\n test('does not resolve sanity package when a React element icon is provided', async () => {\n setupMocks()\n const elementIcon = createElement('svg', null, createElement('path', {d: 'M0 0'}))\n\n await resolveSchemaIcon({icon: elementIcon, title: 'Test', workDir})\n\n expect(mockResolveLocalPackage).not.toHaveBeenCalledWith('sanity', workDir)\n })\n\n test('wraps output with the resolved ThemeProvider and built theme', async () => {\n const {mockTheme} = setupMocks()\n\n const result = await resolveSchemaIcon({title: 'Test', workDir})\n\n expect(result.type).toBe(MockThemeProvider)\n expect(result.props).toHaveProperty('theme', mockTheme)\n })\n\n test('calls createDefaultIcon with title and subtitle when no icon is provided', async () => {\n const {createDefaultIcon} = setupMocks()\n\n await resolveSchemaIcon({subtitle: 'document', title: 'My Type', workDir})\n\n expect(createDefaultIcon).toHaveBeenCalledWith('My Type', 'document')\n })\n\n test('passes empty string as subtitle default to createDefaultIcon', async () => {\n const {createDefaultIcon} = setupMocks()\n\n await resolveSchemaIcon({title: 'My Type', workDir})\n\n expect(createDefaultIcon).toHaveBeenCalledWith('My Type', '')\n })\n})\n"],"names":["createElement","afterEach","describe","expect","test","vi","resolveSchemaIcon","mockResolveLocalPackage","hoisted","fn","mockResolveLocalPackageFrom","mockResolveLocalPackagePath","mock","importOriginal","actual","resolveLocalPackage","resolveLocalPackageFrom","resolveLocalPackagePath","MockThemeProvider","children","setupMocks","mockTheme","color","buildTheme","mockReturnValue","createDefaultIcon","fakeSanityUrl","URL","mockImplementation","pkg","Error","parentUrl","href","ThemeProvider","workDir","clearAllMocks","title","toHaveBeenCalledWith","CustomIcon","span","icon","not","elementIcon","d","result","type","toBe","props","toHaveProperty","subtitle"],"mappings":"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"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { resolveLocalPackage } from '@sanity/cli-core';
|
|
2
|
-
import { resolveIcon } from './iconResolver.js';
|
|
1
|
+
import { doImport, resolveLocalPackage } from '@sanity/cli-core';
|
|
3
2
|
import { transformType } from './schemaTypeTransformer.js';
|
|
3
|
+
const iconResolverPath = new URL('iconResolver.js', import.meta.url).href;
|
|
4
|
+
async function lazyResolveIcon() {
|
|
5
|
+
const mod = await doImport(iconResolverPath);
|
|
6
|
+
return mod.resolveIcon;
|
|
7
|
+
}
|
|
4
8
|
/**
|
|
5
9
|
* Extracts manifest data from an array of workspaces
|
|
6
|
-
*/ export function extractWorkspaceManifest(workspaces, workDir) {
|
|
10
|
+
*/ export async function extractWorkspaceManifest(workspaces, workDir) {
|
|
11
|
+
const resolveIcon = await lazyResolveIcon();
|
|
7
12
|
return Promise.all(workspaces.map(async (workspace)=>{
|
|
8
13
|
const [icon, serializedSchema, serializedTools] = await Promise.all([
|
|
9
14
|
resolveIcon({
|
|
@@ -13,7 +18,7 @@ import { transformType } from './schemaTypeTransformer.js';
|
|
|
13
18
|
workDir
|
|
14
19
|
}),
|
|
15
20
|
extractManifestSchemaTypes(workspace.schema, workDir),
|
|
16
|
-
extractManifestTools(workspace.tools, workDir)
|
|
21
|
+
extractManifestTools(workspace.tools, workDir, resolveIcon)
|
|
17
22
|
]);
|
|
18
23
|
return {
|
|
19
24
|
basePath: workspace.basePath,
|
|
@@ -48,7 +53,8 @@ import { transformType } from './schemaTypeTransformer.js';
|
|
|
48
53
|
}
|
|
49
54
|
/**
|
|
50
55
|
* Extracts tool information from workspace tools
|
|
51
|
-
*/ const extractManifestTools = async (tools, workDir
|
|
56
|
+
*/ const extractManifestTools = async (tools, workDir, resolveIcon)=>{
|
|
57
|
+
return Promise.all(tools.map(async (tool)=>{
|
|
52
58
|
const { __internalApplicationType: type, icon, name, title } = tool;
|
|
53
59
|
return {
|
|
54
60
|
icon: await resolveIcon({
|
|
@@ -61,5 +67,6 @@ import { transformType } from './schemaTypeTransformer.js';
|
|
|
61
67
|
type: type || null
|
|
62
68
|
};
|
|
63
69
|
}));
|
|
70
|
+
};
|
|
64
71
|
|
|
65
72
|
//# sourceMappingURL=extractWorkspaceManifest.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/manifest/extractWorkspaceManifest.ts"],"sourcesContent":["import {resolveLocalPackage} from '@sanity/cli-core'\nimport {type Schema} from '@sanity/types'\nimport {type Workspace} from 'sanity'\n\nimport {resolveIcon} from './iconResolver.js'\nimport {type SchemaIconProps} from './
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/manifest/extractWorkspaceManifest.ts"],"sourcesContent":["import {doImport, resolveLocalPackage} from '@sanity/cli-core'\nimport {type Schema} from '@sanity/types'\nimport {type Workspace} from 'sanity'\n\nimport {type resolveIcon as resolveIconFn} from './iconResolver.js'\nimport {type SchemaIconProps} from './resolveSchemaIcon.js'\nimport {transformType} from './schemaTypeTransformer.js'\nimport {type CreateWorkspaceManifest, type ManifestSchemaType, type ManifestTool} from './types.js'\n\nconst iconResolverPath = new URL('iconResolver.js', import.meta.url).href\n\nasync function lazyResolveIcon(): Promise<typeof resolveIconFn> {\n const mod = await doImport(iconResolverPath)\n return mod.resolveIcon\n}\n\n/**\n * Extracts manifest data from an array of workspaces\n */\nexport async function extractWorkspaceManifest(\n workspaces: Workspace[],\n workDir: string,\n): Promise<CreateWorkspaceManifest[]> {\n const resolveIcon = await lazyResolveIcon()\n return Promise.all(\n workspaces.map(async (workspace) => {\n const [icon, serializedSchema, serializedTools] = await Promise.all([\n resolveIcon({\n icon: workspace.icon,\n subtitle: workspace.subtitle,\n title: workspace.title,\n workDir,\n }),\n extractManifestSchemaTypes(workspace.schema as Schema, workDir),\n extractManifestTools(workspace.tools, workDir, resolveIcon),\n ])\n\n return {\n basePath: workspace.basePath,\n dataset: workspace.dataset,\n icon,\n mediaLibrary: workspace.mediaLibrary,\n name: workspace.name,\n projectId: workspace.projectId,\n schema: serializedSchema,\n subtitle: workspace.subtitle,\n title: workspace.title,\n tools: serializedTools,\n }\n }),\n )\n}\n\n/**\n * Extracts all serializable properties from userland schema types,\n * so they best-effort can be used as definitions for Schema.compile.\n *\n * @internal\n */\nexport async function extractManifestSchemaTypes(\n schema: Schema,\n workDir: string,\n): Promise<ManifestSchemaType[]> {\n const typeNames = schema.getTypeNames()\n const context = {schema}\n\n const {createSchema} = await resolveLocalPackage<typeof import('sanity')>('sanity', workDir)\n\n const studioDefaultTypeNames = createSchema({name: 'default', types: []}).getTypeNames()\n\n return typeNames\n .filter((typeName) => !studioDefaultTypeNames.includes(typeName))\n .map((typeName) => schema.get(typeName))\n .filter((type): type is NonNullable<typeof type> => type !== undefined)\n .map((type) => transformType(type, context))\n}\n\n/**\n * Extracts tool information from workspace tools\n */\nconst extractManifestTools = async (\n tools: Workspace['tools'],\n workDir: string,\n resolveIcon: typeof resolveIconFn,\n): Promise<ManifestTool[]> => {\n return Promise.all(\n tools.map(async (tool) => {\n const {\n __internalApplicationType: type,\n icon,\n name,\n title,\n } = tool as Workspace['tools'][number] & {__internalApplicationType: string}\n return {\n icon: await resolveIcon({\n icon: icon as SchemaIconProps['icon'],\n title,\n workDir,\n }),\n name,\n title,\n type: type || null,\n } satisfies ManifestTool\n }),\n )\n}\n"],"names":["doImport","resolveLocalPackage","transformType","iconResolverPath","URL","url","href","lazyResolveIcon","mod","resolveIcon","extractWorkspaceManifest","workspaces","workDir","Promise","all","map","workspace","icon","serializedSchema","serializedTools","subtitle","title","extractManifestSchemaTypes","schema","extractManifestTools","tools","basePath","dataset","mediaLibrary","name","projectId","typeNames","getTypeNames","context","createSchema","studioDefaultTypeNames","types","filter","typeName","includes","get","type","undefined","tool","__internalApplicationType"],"mappings":"AAAA,SAAQA,QAAQ,EAAEC,mBAAmB,QAAO,mBAAkB;AAM9D,SAAQC,aAAa,QAAO,6BAA4B;AAGxD,MAAMC,mBAAmB,IAAIC,IAAI,mBAAmB,YAAYC,GAAG,EAAEC,IAAI;AAEzE,eAAeC;IACb,MAAMC,MAAM,MAAMR,SAASG;IAC3B,OAAOK,IAAIC,WAAW;AACxB;AAEA;;CAEC,GACD,OAAO,eAAeC,yBACpBC,UAAuB,EACvBC,OAAe;IAEf,MAAMH,cAAc,MAAMF;IAC1B,OAAOM,QAAQC,GAAG,CAChBH,WAAWI,GAAG,CAAC,OAAOC;QACpB,MAAM,CAACC,MAAMC,kBAAkBC,gBAAgB,GAAG,MAAMN,QAAQC,GAAG,CAAC;YAClEL,YAAY;gBACVQ,MAAMD,UAAUC,IAAI;gBACpBG,UAAUJ,UAAUI,QAAQ;gBAC5BC,OAAOL,UAAUK,KAAK;gBACtBT;YACF;YACAU,2BAA2BN,UAAUO,MAAM,EAAYX;YACvDY,qBAAqBR,UAAUS,KAAK,EAAEb,SAASH;SAChD;QAED,OAAO;YACLiB,UAAUV,UAAUU,QAAQ;YAC5BC,SAASX,UAAUW,OAAO;YAC1BV;YACAW,cAAcZ,UAAUY,YAAY;YACpCC,MAAMb,UAAUa,IAAI;YACpBC,WAAWd,UAAUc,SAAS;YAC9BP,QAAQL;YACRE,UAAUJ,UAAUI,QAAQ;YAC5BC,OAAOL,UAAUK,KAAK;YACtBI,OAAON;QACT;IACF;AAEJ;AAEA;;;;;CAKC,GACD,OAAO,eAAeG,2BACpBC,MAAc,EACdX,OAAe;IAEf,MAAMmB,YAAYR,OAAOS,YAAY;IACrC,MAAMC,UAAU;QAACV;IAAM;IAEvB,MAAM,EAACW,YAAY,EAAC,GAAG,MAAMjC,oBAA6C,UAAUW;IAEpF,MAAMuB,yBAAyBD,aAAa;QAACL,MAAM;QAAWO,OAAO,EAAE;IAAA,GAAGJ,YAAY;IAEtF,OAAOD,UACJM,MAAM,CAAC,CAACC,WAAa,CAACH,uBAAuBI,QAAQ,CAACD,WACtDvB,GAAG,CAAC,CAACuB,WAAaf,OAAOiB,GAAG,CAACF,WAC7BD,MAAM,CAAC,CAACI,OAA2CA,SAASC,WAC5D3B,GAAG,CAAC,CAAC0B,OAASvC,cAAcuC,MAAMR;AACvC;AAEA;;CAEC,GACD,MAAMT,uBAAuB,OAC3BC,OACAb,SACAH;IAEA,OAAOI,QAAQC,GAAG,CAChBW,MAAMV,GAAG,CAAC,OAAO4B;QACf,MAAM,EACJC,2BAA2BH,IAAI,EAC/BxB,IAAI,EACJY,IAAI,EACJR,KAAK,EACN,GAAGsB;QACJ,OAAO;YACL1B,MAAM,MAAMR,YAAY;gBACtBQ,MAAMA;gBACNI;gBACAT;YACF;YACAiB;YACAR;YACAoB,MAAMA,QAAQ;QAChB;IACF;AAEJ"}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveLocalPackage } from '@sanity/cli-core';
|
|
2
2
|
import DOMPurify from 'isomorphic-dompurify';
|
|
3
|
-
import { renderToReadableStream } from 'react-dom/server';
|
|
4
3
|
import { manifestDebug } from './debug.js';
|
|
5
4
|
import { config } from './purifyConfig.js';
|
|
6
|
-
import {
|
|
5
|
+
import { resolveSchemaIcon } from './resolveSchemaIcon.js';
|
|
7
6
|
/**
|
|
8
7
|
* Resolves an icon to a sanitized HTML string.
|
|
9
8
|
* Uses react-dom/server to capture styles during SSR.
|
|
9
|
+
*
|
|
10
|
+
* react-dom/server is resolved from the studio's working directory to ensure
|
|
11
|
+
* the same React instance is used by both the server renderer and the studio's
|
|
12
|
+
* components. Using the CLI's own react-dom/server would cause a dual-React
|
|
13
|
+
* instance problem where the dispatcher set by one instance is invisible to the other.
|
|
10
14
|
*/ export const resolveIcon = async (props)=>{
|
|
11
15
|
try {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
const [{ renderToReadableStream }, element] = await Promise.all([
|
|
17
|
+
resolveLocalPackage('react-dom/server', props.workDir),
|
|
18
|
+
resolveSchemaIcon(props)
|
|
19
|
+
]);
|
|
20
|
+
const stream = await renderToReadableStream(element);
|
|
21
|
+
await stream.allReady;
|
|
15
22
|
const reader = stream.getReader();
|
|
16
23
|
const chunks = [];
|
|
17
24
|
while(true){
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/manifest/iconResolver.tsx"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/manifest/iconResolver.tsx"],"sourcesContent":["import {resolveLocalPackage} from '@sanity/cli-core'\nimport DOMPurify from 'isomorphic-dompurify'\n\nimport {manifestDebug} from './debug.js'\nimport {config} from './purifyConfig.js'\nimport {resolveSchemaIcon, type SchemaIconProps} from './resolveSchemaIcon.js'\n\n/**\n * Resolves an icon to a sanitized HTML string.\n * Uses react-dom/server to capture styles during SSR.\n *\n * react-dom/server is resolved from the studio's working directory to ensure\n * the same React instance is used by both the server renderer and the studio's\n * components. Using the CLI's own react-dom/server would cause a dual-React\n * instance problem where the dispatcher set by one instance is invisible to the other.\n */\nexport const resolveIcon = async (props: SchemaIconProps): Promise<string | null> => {\n try {\n const [{renderToReadableStream}, element] = await Promise.all([\n resolveLocalPackage<typeof import('react-dom/server')>('react-dom/server', props.workDir),\n resolveSchemaIcon(props),\n ])\n const stream = await renderToReadableStream(element)\n await stream.allReady\n\n const reader = stream.getReader()\n const chunks: Uint8Array[] = []\n while (true) {\n const {done, value} = await reader.read()\n if (done) break\n chunks.push(value)\n }\n const html = new TextDecoder().decode(Buffer.concat(chunks))\n return DOMPurify.sanitize(html.trim(), config)\n } catch (error) {\n manifestDebug('Error resolving icon', error)\n return null\n }\n}\n"],"names":["resolveLocalPackage","DOMPurify","manifestDebug","config","resolveSchemaIcon","resolveIcon","props","renderToReadableStream","element","Promise","all","workDir","stream","allReady","reader","getReader","chunks","done","value","read","push","html","TextDecoder","decode","Buffer","concat","sanitize","trim","error"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,mBAAkB;AACpD,OAAOC,eAAe,uBAAsB;AAE5C,SAAQC,aAAa,QAAO,aAAY;AACxC,SAAQC,MAAM,QAAO,oBAAmB;AACxC,SAAQC,iBAAiB,QAA6B,yBAAwB;AAE9E;;;;;;;;CAQC,GACD,OAAO,MAAMC,cAAc,OAAOC;IAChC,IAAI;QACF,MAAM,CAAC,EAACC,sBAAsB,EAAC,EAAEC,QAAQ,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAC5DV,oBAAuD,oBAAoBM,MAAMK,OAAO;YACxFP,kBAAkBE;SACnB;QACD,MAAMM,SAAS,MAAML,uBAAuBC;QAC5C,MAAMI,OAAOC,QAAQ;QAErB,MAAMC,SAASF,OAAOG,SAAS;QAC/B,MAAMC,SAAuB,EAAE;QAC/B,MAAO,KAAM;YACX,MAAM,EAACC,IAAI,EAAEC,KAAK,EAAC,GAAG,MAAMJ,OAAOK,IAAI;YACvC,IAAIF,MAAM;YACVD,OAAOI,IAAI,CAACF;QACd;QACA,MAAMG,OAAO,IAAIC,cAAcC,MAAM,CAACC,OAAOC,MAAM,CAACT;QACpD,OAAOf,UAAUyB,QAAQ,CAACL,KAAKM,IAAI,IAAIxB;IACzC,EAAE,OAAOyB,OAAO;QACd1B,cAAc,wBAAwB0B;QACtC,OAAO;IACT;AACF,EAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { resolveLocalPackage, resolveLocalPackageFrom, resolveLocalPackagePath } from '@sanity/cli-core';
|
|
3
|
+
import { isValidElement } from 'react';
|
|
4
|
+
import { isValidElementType } from 'react-is';
|
|
5
|
+
/**
|
|
6
|
+
* Resolves all runtime dependencies from the studio's working directory and
|
|
7
|
+
* returns a React element ready for synchronous server-side rendering.
|
|
8
|
+
*
|
|
9
|
+
* Dependencies like ThemeProvider and sanity components must share the same
|
|
10
|
+
* React instance as the server renderer to avoid dual-React dispatcher issues,
|
|
11
|
+
* so they are resolved from the studio's workDir rather than the CLI's own deps.
|
|
12
|
+
*
|
|
13
|
+
* `\@sanity/ui` is a transitive dependency of sanity and may not be directly
|
|
14
|
+
* accessible from the project root in strict package managers (pnpm, Yarn PnP).
|
|
15
|
+
* We resolve it relative to the sanity package's location in node_modules.
|
|
16
|
+
*
|
|
17
|
+
* This function is async, but the returned element is a plain synchronous
|
|
18
|
+
* component - no async server components required. This keeps compatibility
|
|
19
|
+
* with both React 18 and React 19.
|
|
20
|
+
*/ async function resolveSchemaIcon({ icon, subtitle = '', title, workDir }) {
|
|
21
|
+
// Resolve @sanity/ui via sanity's location, since it's a transitive dep
|
|
22
|
+
// that may not be directly accessible from the project root (pnpm strict mode)
|
|
23
|
+
const sanityUrl = resolveLocalPackagePath('sanity', workDir);
|
|
24
|
+
const [{ ThemeProvider }, { buildTheme }, normalizedIcon] = await Promise.all([
|
|
25
|
+
resolveLocalPackageFrom('@sanity/ui', sanityUrl),
|
|
26
|
+
resolveLocalPackageFrom('@sanity/ui/theme', sanityUrl),
|
|
27
|
+
normalizeIcon(icon, title, subtitle, workDir)
|
|
28
|
+
]);
|
|
29
|
+
const theme = buildTheme();
|
|
30
|
+
return /*#__PURE__*/ _jsx(ThemeProvider, {
|
|
31
|
+
theme: theme,
|
|
32
|
+
children: normalizedIcon
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function normalizeIcon(Icon, title, subtitle, workDir) {
|
|
36
|
+
if (isValidElementType(Icon)) return /*#__PURE__*/ _jsx(Icon, {});
|
|
37
|
+
if (/*#__PURE__*/ isValidElement(Icon)) return Icon;
|
|
38
|
+
const { createDefaultIcon } = await resolveLocalPackage('sanity', workDir);
|
|
39
|
+
return createDefaultIcon(title, subtitle);
|
|
40
|
+
}
|
|
41
|
+
export { resolveSchemaIcon };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=resolveSchemaIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/manifest/resolveSchemaIcon.tsx"],"sourcesContent":["import {\n resolveLocalPackage,\n resolveLocalPackageFrom,\n resolveLocalPackagePath,\n} from '@sanity/cli-core'\nimport {type ComponentType, isValidElement, type ReactNode} from 'react'\nimport {isValidElementType} from 'react-is'\n\ninterface SchemaIconProps {\n title: string\n workDir: string\n\n icon?: ComponentType | ReactNode\n subtitle?: string\n}\n\n/**\n * Resolves all runtime dependencies from the studio's working directory and\n * returns a React element ready for synchronous server-side rendering.\n *\n * Dependencies like ThemeProvider and sanity components must share the same\n * React instance as the server renderer to avoid dual-React dispatcher issues,\n * so they are resolved from the studio's workDir rather than the CLI's own deps.\n *\n * `\\@sanity/ui` is a transitive dependency of sanity and may not be directly\n * accessible from the project root in strict package managers (pnpm, Yarn PnP).\n * We resolve it relative to the sanity package's location in node_modules.\n *\n * This function is async, but the returned element is a plain synchronous\n * component - no async server components required. This keeps compatibility\n * with both React 18 and React 19.\n */\nasync function resolveSchemaIcon({\n icon,\n subtitle = '',\n title,\n workDir,\n}: SchemaIconProps): Promise<React.JSX.Element> {\n // Resolve @sanity/ui via sanity's location, since it's a transitive dep\n // that may not be directly accessible from the project root (pnpm strict mode)\n const sanityUrl = resolveLocalPackagePath('sanity', workDir)\n const [{ThemeProvider}, {buildTheme}, normalizedIcon] = await Promise.all([\n resolveLocalPackageFrom<typeof import('@sanity/ui')>('@sanity/ui', sanityUrl),\n resolveLocalPackageFrom<typeof import('@sanity/ui/theme')>('@sanity/ui/theme', sanityUrl),\n normalizeIcon(icon, title, subtitle, workDir),\n ])\n const theme = buildTheme()\n\n return <ThemeProvider theme={theme}>{normalizedIcon}</ThemeProvider>\n}\n\nasync function normalizeIcon(\n Icon: ComponentType | ReactNode | undefined,\n title: string,\n subtitle: string,\n workDir: string,\n): Promise<React.JSX.Element> {\n if (isValidElementType(Icon)) return <Icon />\n if (isValidElement(Icon)) return Icon\n\n const {createDefaultIcon} = await resolveLocalPackage<typeof import('sanity')>('sanity', workDir)\n\n return createDefaultIcon(title, subtitle)\n}\n\nexport {resolveSchemaIcon}\nexport type {SchemaIconProps}\n"],"names":["resolveLocalPackage","resolveLocalPackageFrom","resolveLocalPackagePath","isValidElement","isValidElementType","resolveSchemaIcon","icon","subtitle","title","workDir","sanityUrl","ThemeProvider","buildTheme","normalizedIcon","Promise","all","normalizeIcon","theme","Icon","createDefaultIcon"],"mappings":";AAAA,SACEA,mBAAmB,EACnBC,uBAAuB,EACvBC,uBAAuB,QAClB,mBAAkB;AACzB,SAA4BC,cAAc,QAAuB,QAAO;AACxE,SAAQC,kBAAkB,QAAO,WAAU;AAU3C;;;;;;;;;;;;;;;CAeC,GACD,eAAeC,kBAAkB,EAC/BC,IAAI,EACJC,WAAW,EAAE,EACbC,KAAK,EACLC,OAAO,EACS;IAChB,wEAAwE;IACxE,+EAA+E;IAC/E,MAAMC,YAAYR,wBAAwB,UAAUO;IACpD,MAAM,CAAC,EAACE,aAAa,EAAC,EAAE,EAACC,UAAU,EAAC,EAAEC,eAAe,GAAG,MAAMC,QAAQC,GAAG,CAAC;QACxEd,wBAAqD,cAAcS;QACnET,wBAA2D,oBAAoBS;QAC/EM,cAAcV,MAAME,OAAOD,UAAUE;KACtC;IACD,MAAMQ,QAAQL;IAEd,qBAAO,KAACD;QAAcM,OAAOA;kBAAQJ;;AACvC;AAEA,eAAeG,cACbE,IAA2C,EAC3CV,KAAa,EACbD,QAAgB,EAChBE,OAAe;IAEf,IAAIL,mBAAmBc,OAAO,qBAAO,KAACA;IACtC,kBAAIf,eAAee,OAAO,OAAOA;IAEjC,MAAM,EAACC,iBAAiB,EAAC,GAAG,MAAMnB,oBAA6C,UAAUS;IAEzF,OAAOU,kBAAkBX,OAAOD;AAClC;AAEA,SAAQF,iBAAiB,GAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { ux } from '@oclif/core/ux';
|
|
3
|
-
import { getProjectCliClient, resolveLocalPackage, subdebug } from '@sanity/cli-core';
|
|
3
|
+
import { doImport, getProjectCliClient, resolveLocalPackage, subdebug } from '@sanity/cli-core';
|
|
4
4
|
import { spinner } from '@sanity/cli-core/ux';
|
|
5
5
|
import { SCHEMA_API_VERSION } from '../../services/schemas.js';
|
|
6
6
|
import { getLocalPackageVersion } from '../../util/getLocalPackageVersion.js';
|
|
7
|
-
|
|
7
|
+
const iconResolverPath = new URL('../manifest/iconResolver.js', import.meta.url).href;
|
|
8
8
|
const debug = subdebug('uploadSchemaToLexicon');
|
|
9
9
|
/**
|
|
10
10
|
* Uploads the schemas to Lexicon and returns the studio manifest
|
|
@@ -52,12 +52,14 @@ const debug = subdebug('uploadSchemaToLexicon');
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
// Lazy import to avoid pulling in @sanity/ui at module load time
|
|
56
|
+
const { resolveIcon } = await doImport(iconResolverPath);
|
|
55
57
|
// Generate studio manifest using the shared utility
|
|
56
58
|
const manifest = await generateStudioManifest({
|
|
57
59
|
buildId: JSON.stringify(Date.now()),
|
|
58
60
|
bundleVersion,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
// @todo replace with import from @sanity/schema/_internal in future
|
|
62
|
+
resolveIcon: async (workspace)=>await resolveIcon({
|
|
61
63
|
icon: workspace.icon,
|
|
62
64
|
subtitle: workspace.subtitle,
|
|
63
65
|
title: workspace.title || workspace.name || 'default',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/schema/uploadSchemaToLexicon.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core/ux'\nimport {getProjectCliClient, resolveLocalPackage, subdebug} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {type StudioManifest, type Workspace} from 'sanity'\n\nimport {SCHEMA_API_VERSION} from '../../services/schemas.js'\nimport {getLocalPackageVersion} from '../../util/getLocalPackageVersion.js'\
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/schema/uploadSchemaToLexicon.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core/ux'\nimport {doImport, getProjectCliClient, resolveLocalPackage, subdebug} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {type StudioManifest, type Workspace} from 'sanity'\n\nimport {SCHEMA_API_VERSION} from '../../services/schemas.js'\nimport {getLocalPackageVersion} from '../../util/getLocalPackageVersion.js'\n\nconst iconResolverPath = new URL('../manifest/iconResolver.js', import.meta.url).href\n\ninterface UploadSchemaToLexiconOptions {\n projectId: string\n workDir: string\n workspaces: Workspace[]\n\n verbose?: boolean\n}\n\nconst debug = subdebug('uploadSchemaToLexicon')\n\n/**\n * Uploads the schemas to Lexicon and returns the studio manifest\n * @param options - The options for the uploadSchemaToLexicon function\n * @returns The studio manifest\n */\nexport async function uploadSchemaToLexicon(\n options: UploadSchemaToLexiconOptions,\n): Promise<StudioManifest | null> {\n const {projectId, verbose, workDir, workspaces} = options\n const spin = spinner('Generating studio manifest').start()\n\n try {\n const schemaDescriptors = new Map<string, string>()\n\n const client = await getProjectCliClient({\n apiVersion: SCHEMA_API_VERSION,\n projectId,\n requestTagPrefix: 'sanity.cli.deploy',\n requireUser: true,\n })\n\n const [bundleVersion, {generateStudioManifest, uploadSchema}] = await Promise.all([\n getLocalPackageVersion('sanity', workDir),\n resolveLocalPackage<typeof import('sanity')>('sanity', workDir),\n ])\n\n if (!bundleVersion) {\n throw new Error('Failed to find sanity version')\n }\n\n for (const workspace of workspaces) {\n const workspaceClient = client.withConfig({\n dataset: workspace.dataset,\n projectId: workspace.projectId,\n })\n\n try {\n debug('Uploading schema to lexicon for workspace %o', {\n dataset: workspace.dataset,\n projectId: workspace.projectId,\n })\n const descriptorId = await uploadSchema(workspace.schema, workspaceClient)\n\n if (!descriptorId) {\n throw new Error(\n `Failed to get schema descriptor ID for workspace \"${workspace.name}\": upload returned empty result`,\n )\n }\n\n schemaDescriptors.set(workspace.name, descriptorId)\n debug(\n `Uploaded schema for workspace \"${workspace.name}\" to Lexicon with descriptor ID: ${descriptorId}`,\n )\n } catch (error) {\n debug('Error uploading schema to lexicon for workspace %o', error)\n const errorMessage = error instanceof Error ? error.message : 'Unknown error'\n throw new Error(\n `Failed to upload schema for workspace \"${workspace.name}\": ${errorMessage}`,\n {cause: error},\n )\n }\n }\n\n // Lazy import to avoid pulling in @sanity/ui at module load time\n const {resolveIcon} = await doImport(iconResolverPath)\n\n // Generate studio manifest using the shared utility\n const manifest = await generateStudioManifest({\n buildId: JSON.stringify(Date.now()),\n bundleVersion,\n // @todo replace with import from @sanity/schema/_internal in future\n resolveIcon: async (workspace) =>\n (await resolveIcon({\n icon: workspace.icon,\n subtitle: workspace.subtitle,\n title: workspace.title || workspace.name || 'default',\n workDir,\n })) ?? undefined,\n resolveSchemaDescriptorId: (workspace) => schemaDescriptors.get(workspace.name),\n workspaces,\n })\n\n spin.succeed('Generated studio manifest')\n\n const studioManifest = manifest.workspaces.length === 0 ? null : manifest\n\n if (verbose) {\n if (studioManifest) {\n for (const workspace of studioManifest.workspaces) {\n ux.stdout(\n styleText(\n 'gray',\n `↳ projectId: ${workspace.projectId}, dataset: ${workspace.dataset}, schemaDescriptorId: ${workspace.schemaDescriptorId}`,\n ),\n )\n }\n } else {\n ux.stdout(`${styleText('gray', '↳ No workspaces found')}`)\n }\n }\n\n return studioManifest\n } catch (error) {\n spin.fail(error instanceof Error ? error.message : 'Unknown error')\n throw error\n }\n}\n"],"names":["styleText","ux","doImport","getProjectCliClient","resolveLocalPackage","subdebug","spinner","SCHEMA_API_VERSION","getLocalPackageVersion","iconResolverPath","URL","url","href","debug","uploadSchemaToLexicon","options","projectId","verbose","workDir","workspaces","spin","start","schemaDescriptors","Map","client","apiVersion","requestTagPrefix","requireUser","bundleVersion","generateStudioManifest","uploadSchema","Promise","all","Error","workspace","workspaceClient","withConfig","dataset","descriptorId","schema","name","set","error","errorMessage","message","cause","resolveIcon","manifest","buildId","JSON","stringify","Date","now","icon","subtitle","title","undefined","resolveSchemaDescriptorId","get","succeed","studioManifest","length","stdout","schemaDescriptorId","fail"],"mappings":"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"}
|