@sanity/cli 6.2.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +299 -303
- package/dist/SanityHelp.js +38 -0
- package/dist/SanityHelp.js.map +1 -1
- package/dist/actions/auth/login/getProvider.js +9 -4
- package/dist/actions/auth/login/getProvider.js.map +1 -1
- package/dist/actions/auth/login/getSSOProvider.js +21 -2
- package/dist/actions/auth/login/getSSOProvider.js.map +1 -1
- package/dist/actions/auth/login/login.js +5 -4
- package/dist/actions/auth/login/login.js.map +1 -1
- package/dist/actions/build/buildApp.js +1 -0
- package/dist/actions/build/buildApp.js.map +1 -1
- package/dist/actions/build/buildStaticFiles.js +2 -1
- package/dist/actions/build/buildStaticFiles.js.map +1 -1
- package/dist/actions/build/renderDocument.js.map +1 -1
- package/dist/actions/build/renderDocumentWorker/components/BasicDocument.js +2 -2
- package/dist/actions/build/renderDocumentWorker/components/BasicDocument.js.map +1 -1
- package/dist/actions/build/renderDocumentWorker/types.js.map +1 -1
- package/dist/actions/build/writeSanityRuntime.js +3 -2
- package/dist/actions/build/writeSanityRuntime.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +53 -3
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/findUserApplicationForStudio.js +10 -4
- package/dist/actions/deploy/findUserApplicationForStudio.js.map +1 -1
- package/dist/actions/dev/startAppDevServer.js +2 -0
- package/dist/actions/dev/startAppDevServer.js.map +1 -1
- package/dist/actions/init/git.js +5 -2
- package/dist/actions/init/git.js.map +1 -1
- package/dist/actions/init/remoteTemplate.js +3 -1
- package/dist/actions/init/remoteTemplate.js.map +1 -1
- package/dist/actions/init/templates/nextjs/index.js +1 -1
- package/dist/actions/init/templates/nextjs/index.js.map +1 -1
- package/dist/actions/manifest/extractAppManifest.js +3 -1
- package/dist/actions/manifest/extractAppManifest.js.map +1 -1
- package/dist/actions/manifest/schemaTypeTransformer.js +2 -1
- package/dist/actions/manifest/schemaTypeTransformer.js.map +1 -1
- package/dist/actions/telemetry/setConsent.js +6 -2
- package/dist/actions/telemetry/setConsent.js.map +1 -1
- package/dist/commands/datasets/alias/create.js +0 -4
- package/dist/commands/datasets/alias/create.js.map +1 -1
- package/dist/commands/datasets/alias/delete.js +1 -5
- package/dist/commands/datasets/alias/delete.js.map +1 -1
- package/dist/commands/datasets/alias/link.js +0 -4
- package/dist/commands/datasets/alias/link.js.map +1 -1
- package/dist/commands/datasets/alias/unlink.js +0 -4
- package/dist/commands/datasets/alias/unlink.js.map +1 -1
- package/dist/commands/datasets/copy.js +1 -1
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/datasets/import.js +45 -15
- package/dist/commands/datasets/import.js.map +1 -1
- package/dist/commands/deploy.js +3 -0
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/init.js +10 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.js +14 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +6 -8
- package/dist/commands/logout.js.map +1 -1
- package/dist/hooks/prerun/setupTelemetry.js +1 -1
- package/dist/hooks/prerun/setupTelemetry.js.map +1 -1
- package/dist/server/devServer.js +2 -1
- package/dist/server/devServer.js.map +1 -1
- package/dist/services/telemetry.js +23 -2
- package/dist/services/telemetry.js.map +1 -1
- package/dist/util/compareDependencyVersions.js +3 -1
- package/dist/util/compareDependencyVersions.js.map +1 -1
- package/dist/util/createExpiringConfig.js +0 -3
- package/dist/util/createExpiringConfig.js.map +1 -1
- package/dist/util/getCliVersion.js +3 -1
- package/dist/util/getCliVersion.js.map +1 -1
- package/dist/util/packageManager/packageManagerChoice.js +1 -2
- package/dist/util/packageManager/packageManagerChoice.js.map +1 -1
- package/dist/util/telemetry/logger.js +13 -0
- package/dist/util/telemetry/logger.js.map +1 -1
- package/oclif.manifest.json +282 -272
- package/package.json +27 -27
- package/dist/actions/graphql/__tests__/fixtures/many-self-refs.js +0 -540
- package/dist/actions/graphql/__tests__/fixtures/many-self-refs.js.map +0 -1
- package/dist/actions/graphql/__tests__/fixtures/test-studio.js +0 -1143
- package/dist/actions/graphql/__tests__/fixtures/test-studio.js.map +0 -1
- package/dist/actions/graphql/__tests__/fixtures/union-refs.js +0 -591
- package/dist/actions/graphql/__tests__/fixtures/union-refs.js.map +0 -1
- package/dist/actions/graphql/__tests__/helpers.js +0 -23
- package/dist/actions/graphql/__tests__/helpers.js.map +0 -1
- package/dist/actions/manifest/__tests__/resolveSchemaIcon.test.js +0 -157
- package/dist/actions/manifest/__tests__/resolveSchemaIcon.test.js.map +0 -1
- package/dist/actions/media/__tests__/createMockClient.js +0 -32
- package/dist/actions/media/__tests__/createMockClient.js.map +0 -1
|
@@ -2,6 +2,7 @@ import { basename, dirname } from 'node:path';
|
|
|
2
2
|
import { styleText } from 'node:util';
|
|
3
3
|
import { createGzip } from 'node:zlib';
|
|
4
4
|
import { CLIError } from '@oclif/core/errors';
|
|
5
|
+
import { exitCodes } from '@sanity/cli-core';
|
|
5
6
|
import { spinner } from '@sanity/cli-core/ux';
|
|
6
7
|
import { pack } from 'tar-fs';
|
|
7
8
|
import { createDeployment } from '../../services/userApplications.js';
|
|
@@ -17,11 +18,11 @@ import { createStudioUserApplication } from './createStudioUserApplication.js';
|
|
|
17
18
|
import { deployDebug } from './deployDebug.js';
|
|
18
19
|
import { deployStudioSchemasAndManifests } from './deployStudioSchemasAndManifests.js';
|
|
19
20
|
import { findUserApplicationForStudio } from './findUserApplicationForStudio.js';
|
|
21
|
+
import { normalizeUrl, validateUrl } from './urlUtils.js';
|
|
20
22
|
export async function deployStudio(options) {
|
|
21
23
|
const { cliConfig, flags, output, projectRoot, sourceDir } = options;
|
|
22
24
|
const workDir = projectRoot.directory;
|
|
23
25
|
const configPath = projectRoot.path;
|
|
24
|
-
const appHost = cliConfig.studioHost;
|
|
25
26
|
const appId = getAppId(cliConfig);
|
|
26
27
|
const projectId = cliConfig.api?.projectId;
|
|
27
28
|
const installedSanityVersion = await getLocalPackageVersion('sanity', workDir);
|
|
@@ -32,6 +33,13 @@ export async function deployStudio(options) {
|
|
|
32
33
|
});
|
|
33
34
|
const isExternal = !!flags.external;
|
|
34
35
|
const urlType = isExternal ? 'external' : 'internal';
|
|
36
|
+
// Resolve the app host from --url flag (takes precedence) or studioHost config
|
|
37
|
+
const appHost = resolveAppHost({
|
|
38
|
+
flags,
|
|
39
|
+
isExternal,
|
|
40
|
+
output,
|
|
41
|
+
studioHost: cliConfig.studioHost
|
|
42
|
+
});
|
|
35
43
|
if (!installedSanityVersion) {
|
|
36
44
|
output.error(`Failed to find installed sanity version`, {
|
|
37
45
|
exit: 1
|
|
@@ -51,9 +59,17 @@ export async function deployStudio(options) {
|
|
|
51
59
|
appId,
|
|
52
60
|
output,
|
|
53
61
|
projectId,
|
|
62
|
+
unattended: !!flags.yes,
|
|
54
63
|
urlType
|
|
55
64
|
});
|
|
56
65
|
if (!userApplication) {
|
|
66
|
+
if (flags.yes) {
|
|
67
|
+
const flagHint = isExternal ? 'Use --url to specify the external studio URL' : 'Use --url to specify the studio hostname';
|
|
68
|
+
output.error(`Cannot prompt for ${isExternal ? 'external studio URL' : 'studio hostname'} in unattended mode. ${flagHint}.`, {
|
|
69
|
+
exit: exitCodes.USAGE_ERROR
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
57
73
|
if (isExternal) {
|
|
58
74
|
output.log('Your project has not been registered with an external studio URL.');
|
|
59
75
|
output.log('Please enter the full URL where your studio is hosted.');
|
|
@@ -164,10 +180,10 @@ export default defineCliConfig({
|
|
|
164
180
|
output.log(`\n${example}`);
|
|
165
181
|
}
|
|
166
182
|
} catch (error) {
|
|
167
|
-
// if the error is a CLIError, we can just output the message and exit
|
|
183
|
+
// if the error is a CLIError, we can just output the message and preserve its exit code
|
|
168
184
|
if (error instanceof CLIError) {
|
|
169
185
|
output.error(error.message, {
|
|
170
|
-
exit:
|
|
186
|
+
exit: error.oclif?.exit ?? exitCodes.RUNTIME_ERROR
|
|
171
187
|
});
|
|
172
188
|
return;
|
|
173
189
|
}
|
|
@@ -178,5 +194,39 @@ export default defineCliConfig({
|
|
|
178
194
|
});
|
|
179
195
|
}
|
|
180
196
|
}
|
|
197
|
+
function resolveAppHost({ flags, isExternal, output, studioHost }) {
|
|
198
|
+
const url = flags.url;
|
|
199
|
+
if (!url) {
|
|
200
|
+
return studioHost;
|
|
201
|
+
}
|
|
202
|
+
if (isExternal) {
|
|
203
|
+
const normalized = normalizeUrl(url);
|
|
204
|
+
const validation = validateUrl(normalized);
|
|
205
|
+
if (validation !== true) {
|
|
206
|
+
output.error(validation, {
|
|
207
|
+
exit: exitCodes.USAGE_ERROR
|
|
208
|
+
});
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
return normalized;
|
|
212
|
+
}
|
|
213
|
+
// For internal deploys, strip protocol prefix and .sanity.studio suffix if present
|
|
214
|
+
const hostname = url.replace(/^https?:\/\//i, '').replace(/\.sanity\.studio\/?$/i, '');
|
|
215
|
+
// If the result still looks like a URL (contains dots), the user likely meant --external
|
|
216
|
+
if (hostname.includes('.')) {
|
|
217
|
+
output.error(`"${hostname}" does not look like a sanity.studio hostname. Did you mean to use --external?`, {
|
|
218
|
+
exit: exitCodes.USAGE_ERROR
|
|
219
|
+
});
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
// Validate hostname characters (alphanumeric and hyphens only)
|
|
223
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i.test(hostname)) {
|
|
224
|
+
output.error(`Invalid studio hostname "${hostname}". Hostnames can only contain letters, numbers, and hyphens.`, {
|
|
225
|
+
exit: exitCodes.USAGE_ERROR
|
|
226
|
+
});
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
return hostname;
|
|
230
|
+
}
|
|
181
231
|
|
|
182
232
|
//# sourceMappingURL=deployStudio.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/deployStudio.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip, type Gzip} from 'node:zlib'\n\nimport {CLIError} from '@oclif/core/errors'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {type StudioManifest} from 'sanity'\nimport {pack} from 'tar-fs'\n\nimport {createDeployment} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {NO_PROJECT_ID} from '../../util/errorMessages.js'\nimport {getLocalPackageVersion} from '../../util/getLocalPackageVersion.js'\nimport {buildStudio} from '../build/buildStudio.js'\nimport {shouldAutoUpdate} from '../build/shouldAutoUpdate.js'\nimport {formatSchemaValidation} from '../schema/formatSchemaValidation.js'\nimport {SchemaExtractionError} from '../schema/utils/SchemaExtractionError.js'\nimport {checkDir} from './checkDir.js'\nimport {createStudioUserApplication} from './createStudioUserApplication.js'\nimport {deployDebug} from './deployDebug.js'\nimport {deployStudioSchemasAndManifests} from './deployStudioSchemasAndManifests.js'\nimport {findUserApplicationForStudio} from './findUserApplicationForStudio.js'\nimport {type DeployAppOptions} from './types.js'\n\nexport async function deployStudio(options: DeployAppOptions) {\n const {cliConfig, flags, output, projectRoot, sourceDir} = options\n\n const workDir = projectRoot.directory\n const configPath = projectRoot.path\n\n const appHost = cliConfig.studioHost\n const appId = getAppId(cliConfig)\n const projectId = cliConfig.api?.projectId\n const installedSanityVersion = await getLocalPackageVersion('sanity', workDir)\n const isAutoUpdating = shouldAutoUpdate({cliConfig, flags, output})\n\n const isExternal = !!flags.external\n const urlType: 'external' | 'internal' = isExternal ? 'external' : 'internal'\n\n if (!installedSanityVersion) {\n output.error(`Failed to find installed sanity version`, {exit: 1})\n return\n }\n\n if (!projectId) {\n output.error(NO_PROJECT_ID, {exit: 1})\n return\n }\n\n let spin = spinner('Verifying local content')\n\n try {\n let userApplication = await findUserApplicationForStudio({\n appHost,\n appId,\n output,\n projectId,\n urlType,\n })\n\n if (!userApplication) {\n if (isExternal) {\n output.log('Your project has not been registered with an external studio URL.')\n output.log('Please enter the full URL where your studio is hosted.')\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log('To deploy your Sanity Studio to our hosted sanity.studio service,')\n output.log('you will need one. Please enter the subdomain you want to use.')\n }\n\n userApplication = await createStudioUserApplication({projectId, urlType})\n\n deployDebug('Created user application', userApplication)\n }\n\n deployDebug('Found user application', userApplication)\n\n // Always build the project, unless --no-build is passed or --external is passed\n const shouldBuild = flags.build && !isExternal\n if (shouldBuild) {\n deployDebug(`Building studio`)\n await buildStudio({\n autoUpdatesEnabled: isAutoUpdating,\n calledFromDeploy: true,\n cliConfig,\n flags,\n outDir: sourceDir,\n output,\n workDir,\n })\n }\n\n let studioManifest: StudioManifest | null = null\n try {\n studioManifest = await deployStudioSchemasAndManifests({\n configPath,\n isExternal,\n outPath: `${sourceDir}/static`,\n projectId,\n schemaRequired: flags['schema-required'],\n verbose: flags.verbose,\n workDir,\n })\n } catch (error) {\n deployDebug('Error deploying studio schemas and manifests', error)\n if (error instanceof SchemaExtractionError) {\n output.error(formatSchemaValidation(error.validation || []), {exit: 1})\n }\n output.error(`Error deploying studio schemas and manifests: ${error}`, {exit: 1})\n }\n\n if (!studioManifest) {\n output.error('Failed to generate studio manifest. Please check your schemas and manifests.', {\n exit: 1,\n })\n }\n\n let tarball: Gzip | undefined\n\n if (!isExternal) {\n // Ensure that the directory exists, is a directory and seems to have valid content\n spin = spin.start()\n try {\n await checkDir(sourceDir)\n spin.succeed()\n } catch (err) {\n spin.fail()\n deployDebug('Error checking directory', err)\n output.error('Error checking directory', {exit: 1})\n }\n\n // Create a tarball of the given directory\n const parentDir = dirname(sourceDir)\n const base = basename(sourceDir)\n tarball = pack(parentDir, {entries: [base]}).pipe(createGzip())\n }\n\n spin = spinner(isExternal ? 'Registering studio' : 'Deploying to sanity.studio').start()\n\n const {location} = await createDeployment({\n applicationId: userApplication.id,\n isApp: false,\n isAutoUpdating,\n manifest: studioManifest,\n projectId,\n tarball,\n version: installedSanityVersion,\n })\n\n spin.succeed()\n\n if (isExternal) {\n output.log(`\\nSuccess! Studio registered`)\n } else {\n output.log(`\\nSuccess! Studio deployed to ${styleText('cyan', location)}`)\n }\n\n if (!appId) {\n const example = `Example:\nexport default defineCliConfig({\n //…\n deployment: {\n ${styleText('cyan', `appId: '${userApplication.id}'`)},\n },\n //…\n})`\n output.log(`\\nAdd ${styleText('cyan', `appId: '${userApplication.id}'`)}`)\n output.log(`to the \\`deployment\\` section in sanity.cli.js or sanity.cli.ts`)\n output.log(`to avoid prompting for application id on next deploy.`)\n output.log(`\\n${example}`)\n }\n } catch (error) {\n // if the error is a CLIError, we can just output the message and exit\n if (error instanceof CLIError) {\n output.error(error.message, {exit: 1})\n return\n }\n\n spin.fail()\n deployDebug('Error deploying studio', error)\n output.error(`Error deploying studio: ${error}`, {exit: 1})\n }\n}\n"],"names":["basename","dirname","styleText","createGzip","CLIError","spinner","pack","createDeployment","getAppId","NO_PROJECT_ID","getLocalPackageVersion","buildStudio","shouldAutoUpdate","formatSchemaValidation","SchemaExtractionError","checkDir","createStudioUserApplication","deployDebug","deployStudioSchemasAndManifests","findUserApplicationForStudio","deployStudio","options","cliConfig","flags","output","projectRoot","sourceDir","workDir","directory","configPath","path","appHost","studioHost","appId","projectId","api","installedSanityVersion","isAutoUpdating","isExternal","external","urlType","error","exit","spin","userApplication","log","shouldBuild","build","autoUpdatesEnabled","calledFromDeploy","outDir","studioManifest","outPath","schemaRequired","verbose","validation","tarball","start","succeed","err","fail","parentDir","base","entries","pipe","location","applicationId","id","isApp","manifest","version","example","message"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/deployStudio.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip, type Gzip} from 'node:zlib'\n\nimport {CLIError} from '@oclif/core/errors'\nimport {exitCodes, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {type StudioManifest} from 'sanity'\nimport {pack} from 'tar-fs'\n\nimport {createDeployment} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {NO_PROJECT_ID} from '../../util/errorMessages.js'\nimport {getLocalPackageVersion} from '../../util/getLocalPackageVersion.js'\nimport {buildStudio} from '../build/buildStudio.js'\nimport {shouldAutoUpdate} from '../build/shouldAutoUpdate.js'\nimport {formatSchemaValidation} from '../schema/formatSchemaValidation.js'\nimport {SchemaExtractionError} from '../schema/utils/SchemaExtractionError.js'\nimport {checkDir} from './checkDir.js'\nimport {createStudioUserApplication} from './createStudioUserApplication.js'\nimport {deployDebug} from './deployDebug.js'\nimport {deployStudioSchemasAndManifests} from './deployStudioSchemasAndManifests.js'\nimport {findUserApplicationForStudio} from './findUserApplicationForStudio.js'\nimport {type DeployAppOptions} from './types.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\nexport async function deployStudio(options: DeployAppOptions) {\n const {cliConfig, flags, output, projectRoot, sourceDir} = options\n\n const workDir = projectRoot.directory\n const configPath = projectRoot.path\n\n const appId = getAppId(cliConfig)\n const projectId = cliConfig.api?.projectId\n const installedSanityVersion = await getLocalPackageVersion('sanity', workDir)\n const isAutoUpdating = shouldAutoUpdate({cliConfig, flags, output})\n\n const isExternal = !!flags.external\n const urlType: 'external' | 'internal' = isExternal ? 'external' : 'internal'\n\n // Resolve the app host from --url flag (takes precedence) or studioHost config\n const appHost = resolveAppHost({flags, isExternal, output, studioHost: cliConfig.studioHost})\n\n if (!installedSanityVersion) {\n output.error(`Failed to find installed sanity version`, {exit: 1})\n return\n }\n\n if (!projectId) {\n output.error(NO_PROJECT_ID, {exit: 1})\n return\n }\n\n let spin = spinner('Verifying local content')\n\n try {\n let userApplication = await findUserApplicationForStudio({\n appHost,\n appId,\n output,\n projectId,\n unattended: !!flags.yes,\n urlType,\n })\n\n if (!userApplication) {\n if (flags.yes) {\n const flagHint = isExternal\n ? 'Use --url to specify the external studio URL'\n : 'Use --url to specify the studio hostname'\n output.error(\n `Cannot prompt for ${isExternal ? 'external studio URL' : 'studio hostname'} in unattended mode. ${flagHint}.`,\n {exit: exitCodes.USAGE_ERROR},\n )\n return\n }\n\n if (isExternal) {\n output.log('Your project has not been registered with an external studio URL.')\n output.log('Please enter the full URL where your studio is hosted.')\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log('To deploy your Sanity Studio to our hosted sanity.studio service,')\n output.log('you will need one. Please enter the subdomain you want to use.')\n }\n\n userApplication = await createStudioUserApplication({projectId, urlType})\n\n deployDebug('Created user application', userApplication)\n }\n\n deployDebug('Found user application', userApplication)\n\n // Always build the project, unless --no-build is passed or --external is passed\n const shouldBuild = flags.build && !isExternal\n if (shouldBuild) {\n deployDebug(`Building studio`)\n await buildStudio({\n autoUpdatesEnabled: isAutoUpdating,\n calledFromDeploy: true,\n cliConfig,\n flags,\n outDir: sourceDir,\n output,\n workDir,\n })\n }\n\n let studioManifest: StudioManifest | null = null\n try {\n studioManifest = await deployStudioSchemasAndManifests({\n configPath,\n isExternal,\n outPath: `${sourceDir}/static`,\n projectId,\n schemaRequired: flags['schema-required'],\n verbose: flags.verbose,\n workDir,\n })\n } catch (error) {\n deployDebug('Error deploying studio schemas and manifests', error)\n if (error instanceof SchemaExtractionError) {\n output.error(formatSchemaValidation(error.validation || []), {exit: 1})\n }\n output.error(`Error deploying studio schemas and manifests: ${error}`, {exit: 1})\n }\n\n if (!studioManifest) {\n output.error('Failed to generate studio manifest. Please check your schemas and manifests.', {\n exit: 1,\n })\n }\n\n let tarball: Gzip | undefined\n\n if (!isExternal) {\n // Ensure that the directory exists, is a directory and seems to have valid content\n spin = spin.start()\n try {\n await checkDir(sourceDir)\n spin.succeed()\n } catch (err) {\n spin.fail()\n deployDebug('Error checking directory', err)\n output.error('Error checking directory', {exit: 1})\n }\n\n // Create a tarball of the given directory\n const parentDir = dirname(sourceDir)\n const base = basename(sourceDir)\n tarball = pack(parentDir, {entries: [base]}).pipe(createGzip())\n }\n\n spin = spinner(isExternal ? 'Registering studio' : 'Deploying to sanity.studio').start()\n\n const {location} = await createDeployment({\n applicationId: userApplication.id,\n isApp: false,\n isAutoUpdating,\n manifest: studioManifest,\n projectId,\n tarball,\n version: installedSanityVersion,\n })\n\n spin.succeed()\n\n if (isExternal) {\n output.log(`\\nSuccess! Studio registered`)\n } else {\n output.log(`\\nSuccess! Studio deployed to ${styleText('cyan', location)}`)\n }\n\n if (!appId) {\n const example = `Example:\nexport default defineCliConfig({\n //…\n deployment: {\n ${styleText('cyan', `appId: '${userApplication.id}'`)},\n },\n //…\n})`\n output.log(`\\nAdd ${styleText('cyan', `appId: '${userApplication.id}'`)}`)\n output.log(`to the \\`deployment\\` section in sanity.cli.js or sanity.cli.ts`)\n output.log(`to avoid prompting for application id on next deploy.`)\n output.log(`\\n${example}`)\n }\n } catch (error) {\n // if the error is a CLIError, we can just output the message and preserve its exit code\n if (error instanceof CLIError) {\n output.error(error.message, {exit: error.oclif?.exit ?? exitCodes.RUNTIME_ERROR})\n return\n }\n\n spin.fail()\n deployDebug('Error deploying studio', error)\n output.error(`Error deploying studio: ${error}`, {exit: 1})\n }\n}\n\nfunction resolveAppHost({\n flags,\n isExternal,\n output,\n studioHost,\n}: {\n flags: DeployAppOptions['flags']\n isExternal: boolean\n output: Output\n studioHost: string | undefined\n}): string | undefined {\n const url = flags.url\n if (!url) {\n return studioHost\n }\n\n if (isExternal) {\n const normalized = normalizeUrl(url)\n const validation = validateUrl(normalized)\n if (validation !== true) {\n output.error(validation, {exit: exitCodes.USAGE_ERROR})\n return undefined\n }\n return normalized\n }\n\n // For internal deploys, strip protocol prefix and .sanity.studio suffix if present\n const hostname = url.replace(/^https?:\\/\\//i, '').replace(/\\.sanity\\.studio\\/?$/i, '')\n\n // If the result still looks like a URL (contains dots), the user likely meant --external\n if (hostname.includes('.')) {\n output.error(\n `\"${hostname}\" does not look like a sanity.studio hostname. Did you mean to use --external?`,\n {exit: exitCodes.USAGE_ERROR},\n )\n return undefined\n }\n\n // Validate hostname characters (alphanumeric and hyphens only)\n if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i.test(hostname)) {\n output.error(\n `Invalid studio hostname \"${hostname}\". Hostnames can only contain letters, numbers, and hyphens.`,\n {exit: exitCodes.USAGE_ERROR},\n )\n return undefined\n }\n\n return hostname\n}\n"],"names":["basename","dirname","styleText","createGzip","CLIError","exitCodes","spinner","pack","createDeployment","getAppId","NO_PROJECT_ID","getLocalPackageVersion","buildStudio","shouldAutoUpdate","formatSchemaValidation","SchemaExtractionError","checkDir","createStudioUserApplication","deployDebug","deployStudioSchemasAndManifests","findUserApplicationForStudio","normalizeUrl","validateUrl","deployStudio","options","cliConfig","flags","output","projectRoot","sourceDir","workDir","directory","configPath","path","appId","projectId","api","installedSanityVersion","isAutoUpdating","isExternal","external","urlType","appHost","resolveAppHost","studioHost","error","exit","spin","userApplication","unattended","yes","flagHint","USAGE_ERROR","log","shouldBuild","build","autoUpdatesEnabled","calledFromDeploy","outDir","studioManifest","outPath","schemaRequired","verbose","validation","tarball","start","succeed","err","fail","parentDir","base","entries","pipe","location","applicationId","id","isApp","manifest","version","example","message","oclif","RUNTIME_ERROR","url","normalized","undefined","hostname","replace","includes","test"],"mappings":"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"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Helper functions to find a user application for a Sanity studio.
|
|
3
|
-
*/ import {
|
|
3
|
+
*/ import { exitCodes } from '@sanity/cli-core';
|
|
4
|
+
import { select, Separator, spinner } from '@sanity/cli-core/ux';
|
|
4
5
|
import { createUserApplication, getUserApplication, getUserApplications } from '../../services/userApplications.js';
|
|
5
6
|
import { deployDebug } from './deployDebug.js';
|
|
6
7
|
import { normalizeUrl, validateUrl } from './urlUtils.js';
|
|
7
8
|
export async function findUserApplicationForStudio(options) {
|
|
8
|
-
const { appHost, appId, output, projectId, urlType = 'internal' } = options;
|
|
9
|
+
const { appHost, appId, output, projectId, unattended = false, urlType = 'internal' } = options;
|
|
9
10
|
const spin = spinner('Checking project info').start();
|
|
10
11
|
const userApplication = await findUserApplication({
|
|
11
12
|
appHost,
|
|
@@ -36,6 +37,11 @@ export async function findUserApplicationForStudio(options) {
|
|
|
36
37
|
if (!userApplications?.length) {
|
|
37
38
|
return null;
|
|
38
39
|
}
|
|
40
|
+
// In unattended mode, we can't prompt the user to select a studio.
|
|
41
|
+
// Return null and let the caller handle the error messaging.
|
|
42
|
+
if (unattended) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
39
45
|
// If there are user applications, allow the user to select one of the existing host names,
|
|
40
46
|
// or to create a new one
|
|
41
47
|
const newLabel = urlType === 'external' ? 'Register new external studio URL' : 'Create new studio hostname';
|
|
@@ -65,7 +71,7 @@ export async function findUserApplicationForStudio(options) {
|
|
|
65
71
|
async function findUserApplication(options) {
|
|
66
72
|
const { appHost, appId, output, projectId, urlType } = options;
|
|
67
73
|
let { spin } = options;
|
|
68
|
-
let userApplication
|
|
74
|
+
let userApplication;
|
|
69
75
|
// If the config has an appId, check for apps with that ID
|
|
70
76
|
if (appId) {
|
|
71
77
|
try {
|
|
@@ -96,7 +102,7 @@ async function findUserApplication(options) {
|
|
|
96
102
|
if (validation !== true) {
|
|
97
103
|
spin.fail();
|
|
98
104
|
output.error(validation, {
|
|
99
|
-
exit:
|
|
105
|
+
exit: exitCodes.USAGE_ERROR
|
|
100
106
|
});
|
|
101
107
|
return null;
|
|
102
108
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/deploy/findUserApplicationForStudio.ts"],"sourcesContent":["/**\n * Helper functions to find a user application for a Sanity studio.\n */\n\nimport {type Output} from '@sanity/cli-core'\nimport {select, Separator, spinner, type SpinnerInstance} from '@sanity/cli-core/ux'\n\nimport {\n createUserApplication,\n getUserApplication,\n getUserApplications,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {deployDebug} from './deployDebug.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\ninterface FindUserApplicationForStudioOptions {\n output: Output\n projectId: string\n\n appHost?: string\n appId?: string\n urlType?: 'external' | 'internal'\n}\n\nexport async function findUserApplicationForStudio(options: FindUserApplicationForStudioOptions) {\n const {appHost, appId, output, projectId, urlType = 'internal'} = options\n\n const spin = spinner('Checking project info').start()\n\n const userApplication = await findUserApplication({\n appHost,\n appId,\n output,\n projectId,\n spin,\n urlType,\n })\n\n spin.succeed()\n\n if (userApplication) {\n return userApplication\n }\n\n // No user application found, so let's list out the existing user applications\n // along with an option to create a new one\n let userApplications: Array<UserApplication> = []\n\n // Get existing user applications (if any),\n // based on the configured project ID\n if (projectId) {\n const allApps = await getUserApplications({\n appType: 'studio',\n projectId,\n })\n // Filter by urlType so external deploys only see external studios and vice versa\n userApplications = allApps?.filter((app) => app.urlType === urlType) ?? []\n }\n\n // If no applications are found, return null\n if (!userApplications?.length) {\n return null\n }\n\n // If there are user applications, allow the user to select one of the existing host names,\n // or to create a new one\n const newLabel =\n urlType === 'external' ? 'Register new external studio URL' : 'Create new studio hostname'\n const selectMessage =\n urlType === 'external'\n ? 'Select existing external studio, or register a new one'\n : 'Select existing studio hostname, or create a new one'\n\n const choices = userApplications.map((app) => ({\n name: app.title ?? app.appHost,\n value: app.appHost,\n }))\n\n const selected = await select({\n choices: [{name: newLabel, value: 'NEW_STUDIO'}, new Separator(), ...choices],\n message: selectMessage,\n })\n\n // If the user wants to create a new deployed application, return null\n if (selected === 'NEW_STUDIO') {\n return null\n }\n\n // Otherwise, return the selected user application\n return userApplications.find((app) => app.appHost === selected)!\n}\n\ninterface FindUserApplicationFromConfigOptions {\n output: Output\n projectId: string\n spin: SpinnerInstance\n urlType: 'external' | 'internal'\n\n appHost?: string\n appId?: string\n}\n\nasync function findUserApplication(\n options: FindUserApplicationFromConfigOptions,\n): Promise<UserApplication | null> {\n const {appHost, appId, output, projectId, urlType} = options\n let {spin} = options\n\n let userApplication: UserApplication | null = null\n\n // If the config has an appId, check for apps with that ID\n if (appId) {\n try {\n userApplication = await getUserApplication({appId, isSdkApp: false, projectId})\n\n if (userApplication) {\n return userApplication\n }\n\n // If appID is specified but no app is found with it, throw an error\n throw new Error(`Cannot find app with app ID ${appId}`)\n } catch (error) {\n spin.fail()\n deployDebug('Error finding user application', error)\n output.error(`Error finding user application: ${error?.message}`, {exit: 1})\n }\n }\n\n // As a fallback, if studioHost (deprecated) is configured, check for apps with that host\n if (appHost) {\n // For external URLs, validate and normalize before lookup/creation\n const resolvedHost = urlType === 'external' ? normalizeUrl(appHost) : appHost\n\n if (urlType === 'external') {\n const validation = validateUrl(resolvedHost)\n if (validation !== true) {\n spin.fail()\n output.error(validation, {exit: 1})\n return null\n }\n }\n\n try {\n userApplication = await getUserApplication({\n appHost: resolvedHost,\n isSdkApp: false,\n projectId,\n })\n\n // We've found the application — return it\n if (userApplication) {\n return userApplication\n }\n\n // Otherwise, try to create an app with the configured host\n try {\n if (urlType === 'external') {\n output.log('Your project has not been registered with an external studio URL.')\n output.log(`Registering ${resolvedHost}`)\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log(`Creating https://${resolvedHost}.sanity.studio`)\n }\n output.log('')\n spin = spinner(\n urlType === 'external' ? 'Registering external studio' : 'Creating studio hostname',\n ).start()\n\n const response = await createUserApplication({\n appType: 'studio',\n body: {\n appHost: resolvedHost,\n type: 'studio',\n urlType,\n },\n projectId,\n })\n spin.succeed()\n\n return response\n } catch (e) {\n spin.fail()\n // if the name is taken, it should return a 409 so we relay to the user\n if ([402, 409].includes(e?.statusCode)) {\n output.error(e?.response?.body?.message || 'Bad request', {exit: 1})\n return null\n }\n // otherwise, it's a fatal error\n deployDebug('Error creating user application from config', e)\n output.error(\n `Error creating user application from config: ${e instanceof Error ? e.message : e}`,\n {exit: 1},\n )\n }\n } catch (error) {\n spin.fail()\n deployDebug('Error finding user application', error)\n output.error(\n `Error finding user application: ${error instanceof Error ? error.message : error.toString()}`,\n {exit: 1},\n )\n }\n }\n\n // If no appID and no appHost, just return and proceed to check for studios with the project ID\n return null\n}\n"],"names":["select","Separator","spinner","createUserApplication","getUserApplication","getUserApplications","deployDebug","normalizeUrl","validateUrl","findUserApplicationForStudio","options","appHost","appId","output","projectId","urlType","spin","start","userApplication","findUserApplication","succeed","userApplications","allApps","appType","filter","app","length","newLabel","selectMessage","choices","map","name","title","value","selected","message","find","isSdkApp","Error","error","fail","exit","resolvedHost","validation","log","response","body","type","e","includes","statusCode","toString"],"mappings":"AAAA;;CAEC,GAGD,SAAQA,MAAM,EAAEC,SAAS,EAAEC,OAAO,QAA6B,sBAAqB;AAEpF,SACEC,qBAAqB,EACrBC,kBAAkB,EAClBC,mBAAmB,QAEd,qCAAoC;AAC3C,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAQC,YAAY,EAAEC,WAAW,QAAO,gBAAe;AAWvD,OAAO,eAAeC,6BAA6BC,OAA4C;IAC7F,MAAM,EAACC,OAAO,EAAEC,KAAK,EAAEC,MAAM,EAAEC,SAAS,EAAEC,UAAU,UAAU,EAAC,GAAGL;IAElE,MAAMM,OAAOd,QAAQ,yBAAyBe,KAAK;IAEnD,MAAMC,kBAAkB,MAAMC,oBAAoB;QAChDR;QACAC;QACAC;QACAC;QACAE;QACAD;IACF;IAEAC,KAAKI,OAAO;IAEZ,IAAIF,iBAAiB;QACnB,OAAOA;IACT;IAEA,8EAA8E;IAC9E,2CAA2C;IAC3C,IAAIG,mBAA2C,EAAE;IAEjD,2CAA2C;IAC3C,qCAAqC;IACrC,IAAIP,WAAW;QACb,MAAMQ,UAAU,MAAMjB,oBAAoB;YACxCkB,SAAS;YACTT;QACF;QACA,iFAAiF;QACjFO,mBAAmBC,SAASE,OAAO,CAACC,MAAQA,IAAIV,OAAO,KAAKA,YAAY,EAAE;IAC5E;IAEA,4CAA4C;IAC5C,IAAI,CAACM,kBAAkBK,QAAQ;QAC7B,OAAO;IACT;IAEA,2FAA2F;IAC3F,yBAAyB;IACzB,MAAMC,WACJZ,YAAY,aAAa,qCAAqC;IAChE,MAAMa,gBACJb,YAAY,aACR,2DACA;IAEN,MAAMc,UAAUR,iBAAiBS,GAAG,CAAC,CAACL,MAAS,CAAA;YAC7CM,MAAMN,IAAIO,KAAK,IAAIP,IAAId,OAAO;YAC9BsB,OAAOR,IAAId,OAAO;QACpB,CAAA;IAEA,MAAMuB,WAAW,MAAMlC,OAAO;QAC5B6B,SAAS;YAAC;gBAACE,MAAMJ;gBAAUM,OAAO;YAAY;YAAG,IAAIhC;eAAgB4B;SAAQ;QAC7EM,SAASP;IACX;IAEA,sEAAsE;IACtE,IAAIM,aAAa,cAAc;QAC7B,OAAO;IACT;IAEA,kDAAkD;IAClD,OAAOb,iBAAiBe,IAAI,CAAC,CAACX,MAAQA,IAAId,OAAO,KAAKuB;AACxD;AAYA,eAAef,oBACbT,OAA6C;IAE7C,MAAM,EAACC,OAAO,EAAEC,KAAK,EAAEC,MAAM,EAAEC,SAAS,EAAEC,OAAO,EAAC,GAAGL;IACrD,IAAI,EAACM,IAAI,EAAC,GAAGN;IAEb,IAAIQ,kBAA0C;IAE9C,0DAA0D;IAC1D,IAAIN,OAAO;QACT,IAAI;YACFM,kBAAkB,MAAMd,mBAAmB;gBAACQ;gBAAOyB,UAAU;gBAAOvB;YAAS;YAE7E,IAAII,iBAAiB;gBACnB,OAAOA;YACT;YAEA,oEAAoE;YACpE,MAAM,IAAIoB,MAAM,CAAC,4BAA4B,EAAE1B,OAAO;QACxD,EAAE,OAAO2B,OAAO;YACdvB,KAAKwB,IAAI;YACTlC,YAAY,kCAAkCiC;YAC9C1B,OAAO0B,KAAK,CAAC,CAAC,gCAAgC,EAAEA,OAAOJ,SAAS,EAAE;gBAACM,MAAM;YAAC;QAC5E;IACF;IAEA,yFAAyF;IACzF,IAAI9B,SAAS;QACX,mEAAmE;QACnE,MAAM+B,eAAe3B,YAAY,aAAaR,aAAaI,WAAWA;QAEtE,IAAII,YAAY,YAAY;YAC1B,MAAM4B,aAAanC,YAAYkC;YAC/B,IAAIC,eAAe,MAAM;gBACvB3B,KAAKwB,IAAI;gBACT3B,OAAO0B,KAAK,CAACI,YAAY;oBAACF,MAAM;gBAAC;gBACjC,OAAO;YACT;QACF;QAEA,IAAI;YACFvB,kBAAkB,MAAMd,mBAAmB;gBACzCO,SAAS+B;gBACTL,UAAU;gBACVvB;YACF;YAEA,0CAA0C;YAC1C,IAAII,iBAAiB;gBACnB,OAAOA;YACT;YAEA,2DAA2D;YAC3D,IAAI;gBACF,IAAIH,YAAY,YAAY;oBAC1BF,OAAO+B,GAAG,CAAC;oBACX/B,OAAO+B,GAAG,CAAC,CAAC,YAAY,EAAEF,cAAc;gBAC1C,OAAO;oBACL7B,OAAO+B,GAAG,CAAC;oBACX/B,OAAO+B,GAAG,CAAC,CAAC,iBAAiB,EAAEF,aAAa,cAAc,CAAC;gBAC7D;gBACA7B,OAAO+B,GAAG,CAAC;gBACX5B,OAAOd,QACLa,YAAY,aAAa,gCAAgC,4BACzDE,KAAK;gBAEP,MAAM4B,WAAW,MAAM1C,sBAAsB;oBAC3CoB,SAAS;oBACTuB,MAAM;wBACJnC,SAAS+B;wBACTK,MAAM;wBACNhC;oBACF;oBACAD;gBACF;gBACAE,KAAKI,OAAO;gBAEZ,OAAOyB;YACT,EAAE,OAAOG,GAAG;gBACVhC,KAAKwB,IAAI;gBACT,uEAAuE;gBACvE,IAAI;oBAAC;oBAAK;iBAAI,CAACS,QAAQ,CAACD,GAAGE,aAAa;oBACtCrC,OAAO0B,KAAK,CAACS,GAAGH,UAAUC,MAAMX,WAAW,eAAe;wBAACM,MAAM;oBAAC;oBAClE,OAAO;gBACT;gBACA,gCAAgC;gBAChCnC,YAAY,+CAA+C0C;gBAC3DnC,OAAO0B,KAAK,CACV,CAAC,6CAA6C,EAAES,aAAaV,QAAQU,EAAEb,OAAO,GAAGa,GAAG,EACpF;oBAACP,MAAM;gBAAC;YAEZ;QACF,EAAE,OAAOF,OAAO;YACdvB,KAAKwB,IAAI;YACTlC,YAAY,kCAAkCiC;YAC9C1B,OAAO0B,KAAK,CACV,CAAC,gCAAgC,EAAEA,iBAAiBD,QAAQC,MAAMJ,OAAO,GAAGI,MAAMY,QAAQ,IAAI,EAC9F;gBAACV,MAAM;YAAC;QAEZ;IACF;IAEA,+FAA+F;IAC/F,OAAO;AACT"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/deploy/findUserApplicationForStudio.ts"],"sourcesContent":["/**\n * Helper functions to find a user application for a Sanity studio.\n */\n\nimport {exitCodes, type Output} from '@sanity/cli-core'\nimport {select, Separator, spinner, type SpinnerInstance} from '@sanity/cli-core/ux'\n\nimport {\n createUserApplication,\n getUserApplication,\n getUserApplications,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {deployDebug} from './deployDebug.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\ninterface FindUserApplicationForStudioOptions {\n output: Output\n projectId: string\n\n appHost?: string\n appId?: string\n unattended?: boolean\n urlType?: 'external' | 'internal'\n}\n\nexport async function findUserApplicationForStudio(options: FindUserApplicationForStudioOptions) {\n const {appHost, appId, output, projectId, unattended = false, urlType = 'internal'} = options\n\n const spin = spinner('Checking project info').start()\n\n const userApplication = await findUserApplication({\n appHost,\n appId,\n output,\n projectId,\n spin,\n urlType,\n })\n\n spin.succeed()\n\n if (userApplication) {\n return userApplication\n }\n\n // No user application found, so let's list out the existing user applications\n // along with an option to create a new one\n let userApplications: Array<UserApplication> = []\n\n // Get existing user applications (if any),\n // based on the configured project ID\n if (projectId) {\n const allApps = await getUserApplications({\n appType: 'studio',\n projectId,\n })\n // Filter by urlType so external deploys only see external studios and vice versa\n userApplications = allApps?.filter((app) => app.urlType === urlType) ?? []\n }\n\n // If no applications are found, return null\n if (!userApplications?.length) {\n return null\n }\n\n // In unattended mode, we can't prompt the user to select a studio.\n // Return null and let the caller handle the error messaging.\n if (unattended) {\n return null\n }\n\n // If there are user applications, allow the user to select one of the existing host names,\n // or to create a new one\n const newLabel =\n urlType === 'external' ? 'Register new external studio URL' : 'Create new studio hostname'\n const selectMessage =\n urlType === 'external'\n ? 'Select existing external studio, or register a new one'\n : 'Select existing studio hostname, or create a new one'\n\n const choices = userApplications.map((app) => ({\n name: app.title ?? app.appHost,\n value: app.appHost,\n }))\n\n const selected = await select({\n choices: [{name: newLabel, value: 'NEW_STUDIO'}, new Separator(), ...choices],\n message: selectMessage,\n })\n\n // If the user wants to create a new deployed application, return null\n if (selected === 'NEW_STUDIO') {\n return null\n }\n\n // Otherwise, return the selected user application\n return userApplications.find((app) => app.appHost === selected)!\n}\n\ninterface FindUserApplicationFromConfigOptions {\n output: Output\n projectId: string\n spin: SpinnerInstance\n urlType: 'external' | 'internal'\n\n appHost?: string\n appId?: string\n}\n\nasync function findUserApplication(\n options: FindUserApplicationFromConfigOptions,\n): Promise<UserApplication | null> {\n const {appHost, appId, output, projectId, urlType} = options\n let {spin} = options\n\n let userApplication: UserApplication | null\n\n // If the config has an appId, check for apps with that ID\n if (appId) {\n try {\n userApplication = await getUserApplication({appId, isSdkApp: false, projectId})\n\n if (userApplication) {\n return userApplication\n }\n\n // If appID is specified but no app is found with it, throw an error\n throw new Error(`Cannot find app with app ID ${appId}`)\n } catch (error) {\n spin.fail()\n deployDebug('Error finding user application', error)\n output.error(`Error finding user application: ${error?.message}`, {exit: 1})\n }\n }\n\n // As a fallback, if studioHost (deprecated) is configured, check for apps with that host\n if (appHost) {\n // For external URLs, validate and normalize before lookup/creation\n const resolvedHost = urlType === 'external' ? normalizeUrl(appHost) : appHost\n\n if (urlType === 'external') {\n const validation = validateUrl(resolvedHost)\n if (validation !== true) {\n spin.fail()\n output.error(validation, {exit: exitCodes.USAGE_ERROR})\n return null\n }\n }\n\n try {\n userApplication = await getUserApplication({\n appHost: resolvedHost,\n isSdkApp: false,\n projectId,\n })\n\n // We've found the application — return it\n if (userApplication) {\n return userApplication\n }\n\n // Otherwise, try to create an app with the configured host\n try {\n if (urlType === 'external') {\n output.log('Your project has not been registered with an external studio URL.')\n output.log(`Registering ${resolvedHost}`)\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log(`Creating https://${resolvedHost}.sanity.studio`)\n }\n output.log('')\n spin = spinner(\n urlType === 'external' ? 'Registering external studio' : 'Creating studio hostname',\n ).start()\n\n const response = await createUserApplication({\n appType: 'studio',\n body: {\n appHost: resolvedHost,\n type: 'studio',\n urlType,\n },\n projectId,\n })\n spin.succeed()\n\n return response\n } catch (e) {\n spin.fail()\n // if the name is taken, it should return a 409 so we relay to the user\n if ([402, 409].includes(e?.statusCode)) {\n output.error(e?.response?.body?.message || 'Bad request', {exit: 1})\n return null\n }\n // otherwise, it's a fatal error\n deployDebug('Error creating user application from config', e)\n output.error(\n `Error creating user application from config: ${e instanceof Error ? e.message : e}`,\n {exit: 1},\n )\n }\n } catch (error) {\n spin.fail()\n deployDebug('Error finding user application', error)\n output.error(\n `Error finding user application: ${error instanceof Error ? error.message : error.toString()}`,\n {exit: 1},\n )\n }\n }\n\n // If no appID and no appHost, just return and proceed to check for studios with the project ID\n return null\n}\n"],"names":["exitCodes","select","Separator","spinner","createUserApplication","getUserApplication","getUserApplications","deployDebug","normalizeUrl","validateUrl","findUserApplicationForStudio","options","appHost","appId","output","projectId","unattended","urlType","spin","start","userApplication","findUserApplication","succeed","userApplications","allApps","appType","filter","app","length","newLabel","selectMessage","choices","map","name","title","value","selected","message","find","isSdkApp","Error","error","fail","exit","resolvedHost","validation","USAGE_ERROR","log","response","body","type","e","includes","statusCode","toString"],"mappings":"AAAA;;CAEC,GAED,SAAQA,SAAS,QAAoB,mBAAkB;AACvD,SAAQC,MAAM,EAAEC,SAAS,EAAEC,OAAO,QAA6B,sBAAqB;AAEpF,SACEC,qBAAqB,EACrBC,kBAAkB,EAClBC,mBAAmB,QAEd,qCAAoC;AAC3C,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAQC,YAAY,EAAEC,WAAW,QAAO,gBAAe;AAYvD,OAAO,eAAeC,6BAA6BC,OAA4C;IAC7F,MAAM,EAACC,OAAO,EAAEC,KAAK,EAAEC,MAAM,EAAEC,SAAS,EAAEC,aAAa,KAAK,EAAEC,UAAU,UAAU,EAAC,GAAGN;IAEtF,MAAMO,OAAOf,QAAQ,yBAAyBgB,KAAK;IAEnD,MAAMC,kBAAkB,MAAMC,oBAAoB;QAChDT;QACAC;QACAC;QACAC;QACAG;QACAD;IACF;IAEAC,KAAKI,OAAO;IAEZ,IAAIF,iBAAiB;QACnB,OAAOA;IACT;IAEA,8EAA8E;IAC9E,2CAA2C;IAC3C,IAAIG,mBAA2C,EAAE;IAEjD,2CAA2C;IAC3C,qCAAqC;IACrC,IAAIR,WAAW;QACb,MAAMS,UAAU,MAAMlB,oBAAoB;YACxCmB,SAAS;YACTV;QACF;QACA,iFAAiF;QACjFQ,mBAAmBC,SAASE,OAAO,CAACC,MAAQA,IAAIV,OAAO,KAAKA,YAAY,EAAE;IAC5E;IAEA,4CAA4C;IAC5C,IAAI,CAACM,kBAAkBK,QAAQ;QAC7B,OAAO;IACT;IAEA,mEAAmE;IACnE,6DAA6D;IAC7D,IAAIZ,YAAY;QACd,OAAO;IACT;IAEA,2FAA2F;IAC3F,yBAAyB;IACzB,MAAMa,WACJZ,YAAY,aAAa,qCAAqC;IAChE,MAAMa,gBACJb,YAAY,aACR,2DACA;IAEN,MAAMc,UAAUR,iBAAiBS,GAAG,CAAC,CAACL,MAAS,CAAA;YAC7CM,MAAMN,IAAIO,KAAK,IAAIP,IAAIf,OAAO;YAC9BuB,OAAOR,IAAIf,OAAO;QACpB,CAAA;IAEA,MAAMwB,WAAW,MAAMnC,OAAO;QAC5B8B,SAAS;YAAC;gBAACE,MAAMJ;gBAAUM,OAAO;YAAY;YAAG,IAAIjC;eAAgB6B;SAAQ;QAC7EM,SAASP;IACX;IAEA,sEAAsE;IACtE,IAAIM,aAAa,cAAc;QAC7B,OAAO;IACT;IAEA,kDAAkD;IAClD,OAAOb,iBAAiBe,IAAI,CAAC,CAACX,MAAQA,IAAIf,OAAO,KAAKwB;AACxD;AAYA,eAAef,oBACbV,OAA6C;IAE7C,MAAM,EAACC,OAAO,EAAEC,KAAK,EAAEC,MAAM,EAAEC,SAAS,EAAEE,OAAO,EAAC,GAAGN;IACrD,IAAI,EAACO,IAAI,EAAC,GAAGP;IAEb,IAAIS;IAEJ,0DAA0D;IAC1D,IAAIP,OAAO;QACT,IAAI;YACFO,kBAAkB,MAAMf,mBAAmB;gBAACQ;gBAAO0B,UAAU;gBAAOxB;YAAS;YAE7E,IAAIK,iBAAiB;gBACnB,OAAOA;YACT;YAEA,oEAAoE;YACpE,MAAM,IAAIoB,MAAM,CAAC,4BAA4B,EAAE3B,OAAO;QACxD,EAAE,OAAO4B,OAAO;YACdvB,KAAKwB,IAAI;YACTnC,YAAY,kCAAkCkC;YAC9C3B,OAAO2B,KAAK,CAAC,CAAC,gCAAgC,EAAEA,OAAOJ,SAAS,EAAE;gBAACM,MAAM;YAAC;QAC5E;IACF;IAEA,yFAAyF;IACzF,IAAI/B,SAAS;QACX,mEAAmE;QACnE,MAAMgC,eAAe3B,YAAY,aAAaT,aAAaI,WAAWA;QAEtE,IAAIK,YAAY,YAAY;YAC1B,MAAM4B,aAAapC,YAAYmC;YAC/B,IAAIC,eAAe,MAAM;gBACvB3B,KAAKwB,IAAI;gBACT5B,OAAO2B,KAAK,CAACI,YAAY;oBAACF,MAAM3C,UAAU8C,WAAW;gBAAA;gBACrD,OAAO;YACT;QACF;QAEA,IAAI;YACF1B,kBAAkB,MAAMf,mBAAmB;gBACzCO,SAASgC;gBACTL,UAAU;gBACVxB;YACF;YAEA,0CAA0C;YAC1C,IAAIK,iBAAiB;gBACnB,OAAOA;YACT;YAEA,2DAA2D;YAC3D,IAAI;gBACF,IAAIH,YAAY,YAAY;oBAC1BH,OAAOiC,GAAG,CAAC;oBACXjC,OAAOiC,GAAG,CAAC,CAAC,YAAY,EAAEH,cAAc;gBAC1C,OAAO;oBACL9B,OAAOiC,GAAG,CAAC;oBACXjC,OAAOiC,GAAG,CAAC,CAAC,iBAAiB,EAAEH,aAAa,cAAc,CAAC;gBAC7D;gBACA9B,OAAOiC,GAAG,CAAC;gBACX7B,OAAOf,QACLc,YAAY,aAAa,gCAAgC,4BACzDE,KAAK;gBAEP,MAAM6B,WAAW,MAAM5C,sBAAsB;oBAC3CqB,SAAS;oBACTwB,MAAM;wBACJrC,SAASgC;wBACTM,MAAM;wBACNjC;oBACF;oBACAF;gBACF;gBACAG,KAAKI,OAAO;gBAEZ,OAAO0B;YACT,EAAE,OAAOG,GAAG;gBACVjC,KAAKwB,IAAI;gBACT,uEAAuE;gBACvE,IAAI;oBAAC;oBAAK;iBAAI,CAACU,QAAQ,CAACD,GAAGE,aAAa;oBACtCvC,OAAO2B,KAAK,CAACU,GAAGH,UAAUC,MAAMZ,WAAW,eAAe;wBAACM,MAAM;oBAAC;oBAClE,OAAO;gBACT;gBACA,gCAAgC;gBAChCpC,YAAY,+CAA+C4C;gBAC3DrC,OAAO2B,KAAK,CACV,CAAC,6CAA6C,EAAEU,aAAaX,QAAQW,EAAEd,OAAO,GAAGc,GAAG,EACpF;oBAACR,MAAM;gBAAC;YAEZ;QACF,EAAE,OAAOF,OAAO;YACdvB,KAAKwB,IAAI;YACTnC,YAAY,kCAAkCkC;YAC9C3B,OAAO2B,KAAK,CACV,CAAC,gCAAgC,EAAEA,iBAAiBD,QAAQC,MAAMJ,OAAO,GAAGI,MAAMa,QAAQ,IAAI,EAC9F;gBAACX,MAAM;YAAC;QAEZ;IACF;IAEA,+FAA+F;IAC/F,OAAO;AACT"}
|
|
@@ -28,8 +28,10 @@ export async function startAppDevServer(options) {
|
|
|
28
28
|
});
|
|
29
29
|
try {
|
|
30
30
|
output.log('Starting dev server');
|
|
31
|
+
const appTitle = cliConfig && 'app' in cliConfig ? cliConfig.app?.title : undefined;
|
|
31
32
|
const { close, server } = await startDevServer({
|
|
32
33
|
...config,
|
|
34
|
+
appTitle,
|
|
33
35
|
isApp: true
|
|
34
36
|
});
|
|
35
37
|
const { port } = server.config.server;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/dev/startAppDevServer.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {startDevServer} from '../../server/devServer.js'\nimport {gracefulServerDeath} from '../../server/gracefulServerDeath.js'\nimport {devDebug} from './devDebug.js'\nimport {getDashboardAppURL} from './getDashboardAppUrl.js'\nimport {getDevServerConfig} from './getDevServerConfig.js'\nimport {type DevActionOptions} from './types.js'\n\nexport async function startAppDevServer(\n options: DevActionOptions,\n): Promise<{close?: () => Promise<void>}> {\n const {cliConfig, flags, output, workDir} = options\n\n if (!flags['load-in-dashboard']) {\n output.warn(`Apps cannot run without the Sanity dashboard`)\n output.warn(`Starting dev server with the --load-in-dashboard flag set to true`)\n }\n\n let organizationId: string | undefined\n if (cliConfig && 'app' in cliConfig && cliConfig.app?.organizationId) {\n organizationId = cliConfig.app.organizationId\n }\n\n if (!organizationId) {\n output.error(`Apps require an organization ID (orgId) specified in your sanity.cli.ts file`, {\n exit: 1,\n })\n return {}\n }\n\n const config = getDevServerConfig({cliConfig, flags, output, workDir})\n\n try {\n output.log('Starting dev server')\n\n const {close, server} = await startDevServer({...config, isApp: true})\n\n const {port} = server.config.server\n const httpHost = config.httpHost || 'localhost'\n\n const dashboardAppUrl = await getDashboardAppURL({\n httpHost,\n httpPort: port,\n organizationId,\n })\n output.log(`Dev server started on port ${port}`)\n output.log(`View your app in the Sanity dashboard here:`)\n output.log(styleText(['blue', 'underline'], dashboardAppUrl))\n\n return {close}\n } catch (err) {\n devDebug('Error starting app dev server', err)\n throw gracefulServerDeath('dev', config.httpHost, config.httpPort, err)\n }\n}\n"],"names":["styleText","startDevServer","gracefulServerDeath","devDebug","getDashboardAppURL","getDevServerConfig","startAppDevServer","options","cliConfig","flags","output","workDir","warn","organizationId","app","error","exit","config","log","close","server","isApp","port","httpHost","dashboardAppUrl","httpPort","err"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,cAAc,QAAO,4BAA2B;AACxD,SAAQC,mBAAmB,QAAO,sCAAqC;AACvE,SAAQC,QAAQ,QAAO,gBAAe;AACtC,SAAQC,kBAAkB,QAAO,0BAAyB;AAC1D,SAAQC,kBAAkB,QAAO,0BAAyB;AAG1D,OAAO,eAAeC,kBACpBC,OAAyB;IAEzB,MAAM,EAACC,SAAS,EAAEC,KAAK,EAAEC,MAAM,EAAEC,OAAO,EAAC,GAAGJ;IAE5C,IAAI,CAACE,KAAK,CAAC,oBAAoB,EAAE;QAC/BC,OAAOE,IAAI,CAAC,CAAC,4CAA4C,CAAC;QAC1DF,OAAOE,IAAI,CAAC,CAAC,iEAAiE,CAAC;IACjF;IAEA,IAAIC;IACJ,IAAIL,aAAa,SAASA,aAAaA,UAAUM,GAAG,EAAED,gBAAgB;QACpEA,iBAAiBL,UAAUM,GAAG,CAACD,cAAc;IAC/C;IAEA,IAAI,CAACA,gBAAgB;QACnBH,OAAOK,KAAK,CAAC,CAAC,4EAA4E,CAAC,EAAE;YAC3FC,MAAM;QACR;QACA,OAAO,CAAC;IACV;IAEA,MAAMC,SAASZ,mBAAmB;QAACG;QAAWC;QAAOC;QAAQC;IAAO;IAEpE,IAAI;QACFD,OAAOQ,GAAG,CAAC;QAEX,MAAM,EAACC,KAAK,EAAEC,MAAM,EAAC,GAAG,
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/dev/startAppDevServer.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {startDevServer} from '../../server/devServer.js'\nimport {gracefulServerDeath} from '../../server/gracefulServerDeath.js'\nimport {devDebug} from './devDebug.js'\nimport {getDashboardAppURL} from './getDashboardAppUrl.js'\nimport {getDevServerConfig} from './getDevServerConfig.js'\nimport {type DevActionOptions} from './types.js'\n\nexport async function startAppDevServer(\n options: DevActionOptions,\n): Promise<{close?: () => Promise<void>}> {\n const {cliConfig, flags, output, workDir} = options\n\n if (!flags['load-in-dashboard']) {\n output.warn(`Apps cannot run without the Sanity dashboard`)\n output.warn(`Starting dev server with the --load-in-dashboard flag set to true`)\n }\n\n let organizationId: string | undefined\n if (cliConfig && 'app' in cliConfig && cliConfig.app?.organizationId) {\n organizationId = cliConfig.app.organizationId\n }\n\n if (!organizationId) {\n output.error(`Apps require an organization ID (orgId) specified in your sanity.cli.ts file`, {\n exit: 1,\n })\n return {}\n }\n\n const config = getDevServerConfig({cliConfig, flags, output, workDir})\n\n try {\n output.log('Starting dev server')\n\n const appTitle = cliConfig && 'app' in cliConfig ? cliConfig.app?.title : undefined\n const {close, server} = await startDevServer({...config, appTitle, isApp: true})\n\n const {port} = server.config.server\n const httpHost = config.httpHost || 'localhost'\n\n const dashboardAppUrl = await getDashboardAppURL({\n httpHost,\n httpPort: port,\n organizationId,\n })\n output.log(`Dev server started on port ${port}`)\n output.log(`View your app in the Sanity dashboard here:`)\n output.log(styleText(['blue', 'underline'], dashboardAppUrl))\n\n return {close}\n } catch (err) {\n devDebug('Error starting app dev server', err)\n throw gracefulServerDeath('dev', config.httpHost, config.httpPort, err)\n }\n}\n"],"names":["styleText","startDevServer","gracefulServerDeath","devDebug","getDashboardAppURL","getDevServerConfig","startAppDevServer","options","cliConfig","flags","output","workDir","warn","organizationId","app","error","exit","config","log","appTitle","title","undefined","close","server","isApp","port","httpHost","dashboardAppUrl","httpPort","err"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,cAAc,QAAO,4BAA2B;AACxD,SAAQC,mBAAmB,QAAO,sCAAqC;AACvE,SAAQC,QAAQ,QAAO,gBAAe;AACtC,SAAQC,kBAAkB,QAAO,0BAAyB;AAC1D,SAAQC,kBAAkB,QAAO,0BAAyB;AAG1D,OAAO,eAAeC,kBACpBC,OAAyB;IAEzB,MAAM,EAACC,SAAS,EAAEC,KAAK,EAAEC,MAAM,EAAEC,OAAO,EAAC,GAAGJ;IAE5C,IAAI,CAACE,KAAK,CAAC,oBAAoB,EAAE;QAC/BC,OAAOE,IAAI,CAAC,CAAC,4CAA4C,CAAC;QAC1DF,OAAOE,IAAI,CAAC,CAAC,iEAAiE,CAAC;IACjF;IAEA,IAAIC;IACJ,IAAIL,aAAa,SAASA,aAAaA,UAAUM,GAAG,EAAED,gBAAgB;QACpEA,iBAAiBL,UAAUM,GAAG,CAACD,cAAc;IAC/C;IAEA,IAAI,CAACA,gBAAgB;QACnBH,OAAOK,KAAK,CAAC,CAAC,4EAA4E,CAAC,EAAE;YAC3FC,MAAM;QACR;QACA,OAAO,CAAC;IACV;IAEA,MAAMC,SAASZ,mBAAmB;QAACG;QAAWC;QAAOC;QAAQC;IAAO;IAEpE,IAAI;QACFD,OAAOQ,GAAG,CAAC;QAEX,MAAMC,WAAWX,aAAa,SAASA,YAAYA,UAAUM,GAAG,EAAEM,QAAQC;QAC1E,MAAM,EAACC,KAAK,EAAEC,MAAM,EAAC,GAAG,MAAMtB,eAAe;YAAC,GAAGgB,MAAM;YAAEE;YAAUK,OAAO;QAAI;QAE9E,MAAM,EAACC,IAAI,EAAC,GAAGF,OAAON,MAAM,CAACM,MAAM;QACnC,MAAMG,WAAWT,OAAOS,QAAQ,IAAI;QAEpC,MAAMC,kBAAkB,MAAMvB,mBAAmB;YAC/CsB;YACAE,UAAUH;YACVZ;QACF;QACAH,OAAOQ,GAAG,CAAC,CAAC,2BAA2B,EAAEO,MAAM;QAC/Cf,OAAOQ,GAAG,CAAC,CAAC,2CAA2C,CAAC;QACxDR,OAAOQ,GAAG,CAAClB,UAAU;YAAC;YAAQ;SAAY,EAAE2B;QAE5C,OAAO;YAACL;QAAK;IACf,EAAE,OAAOO,KAAK;QACZ1B,SAAS,iCAAiC0B;QAC1C,MAAM3B,oBAAoB,OAAOe,OAAOS,QAAQ,EAAET,OAAOW,QAAQ,EAAEC;IACrE;AACF"}
|
package/dist/actions/init/git.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync, execSync } from 'node:child_process';
|
|
2
|
+
import { rmSync } from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
|
-
import { rimrafSync } from 'rimraf';
|
|
4
4
|
const defaultCommitMessage = 'feat: bootstrap sanity studio';
|
|
5
5
|
export function tryGitInit(rootDir, commitMessage) {
|
|
6
6
|
const execOptions = {
|
|
@@ -29,7 +29,10 @@ export function tryGitInit(rootDir, commitMessage) {
|
|
|
29
29
|
} catch {
|
|
30
30
|
if (didInit) {
|
|
31
31
|
try {
|
|
32
|
-
|
|
32
|
+
rmSync(path.join(rootDir, '.git'), {
|
|
33
|
+
force: true,
|
|
34
|
+
recursive: true
|
|
35
|
+
});
|
|
33
36
|
} catch {
|
|
34
37
|
// intentional noop
|
|
35
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/init/git.ts"],"sourcesContent":["import {execFileSync, execSync, type ExecSyncOptions} from 'node:child_process'\nimport
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/init/git.ts"],"sourcesContent":["import {execFileSync, execSync, type ExecSyncOptions} from 'node:child_process'\nimport {rmSync} from 'node:fs'\nimport path from 'node:path'\n\nconst defaultCommitMessage = 'feat: bootstrap sanity studio'\n\nexport function tryGitInit(rootDir: string, commitMessage?: string): boolean {\n const execOptions: ExecSyncOptions = {cwd: rootDir, stdio: 'ignore'}\n\n let didInit = false\n try {\n execSync('git --version', execOptions)\n if (isInGitRepository(rootDir) || isInMercurialRepository(rootDir)) {\n return false\n }\n\n execSync('git init', execOptions)\n didInit = true\n\n execSync('git checkout -b main', execOptions)\n\n execSync('git add -A', execOptions)\n execFileSync('git', ['commit', '-m', commitMessage || defaultCommitMessage], {\n cwd: rootDir,\n stdio: 'ignore',\n })\n return true\n } catch {\n if (didInit) {\n try {\n rmSync(path.join(rootDir, '.git'), {force: true, recursive: true})\n } catch {\n // intentional noop\n }\n }\n return false\n }\n}\n\nfunction isInGitRepository(rootDir: string): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', {cwd: rootDir, stdio: 'ignore'})\n return true\n } catch {\n // intentional noop\n }\n return false\n}\n\nfunction isInMercurialRepository(rootDir: string): boolean {\n try {\n execSync('hg --cwd . root', {cwd: rootDir, stdio: 'ignore'})\n return true\n } catch {\n // intentional noop\n }\n return false\n}\n"],"names":["execFileSync","execSync","rmSync","path","defaultCommitMessage","tryGitInit","rootDir","commitMessage","execOptions","cwd","stdio","didInit","isInGitRepository","isInMercurialRepository","join","force","recursive"],"mappings":"AAAA,SAAQA,YAAY,EAAEC,QAAQ,QAA6B,qBAAoB;AAC/E,SAAQC,MAAM,QAAO,UAAS;AAC9B,OAAOC,UAAU,YAAW;AAE5B,MAAMC,uBAAuB;AAE7B,OAAO,SAASC,WAAWC,OAAe,EAAEC,aAAsB;IAChE,MAAMC,cAA+B;QAACC,KAAKH;QAASI,OAAO;IAAQ;IAEnE,IAAIC,UAAU;IACd,IAAI;QACFV,SAAS,iBAAiBO;QAC1B,IAAII,kBAAkBN,YAAYO,wBAAwBP,UAAU;YAClE,OAAO;QACT;QAEAL,SAAS,YAAYO;QACrBG,UAAU;QAEVV,SAAS,wBAAwBO;QAEjCP,SAAS,cAAcO;QACvBR,aAAa,OAAO;YAAC;YAAU;YAAMO,iBAAiBH;SAAqB,EAAE;YAC3EK,KAAKH;YACLI,OAAO;QACT;QACA,OAAO;IACT,EAAE,OAAM;QACN,IAAIC,SAAS;YACX,IAAI;gBACFT,OAAOC,KAAKW,IAAI,CAACR,SAAS,SAAS;oBAACS,OAAO;oBAAMC,WAAW;gBAAI;YAClE,EAAE,OAAM;YACN,mBAAmB;YACrB;QACF;QACA,OAAO;IACT;AACF;AAEA,SAASJ,kBAAkBN,OAAe;IACxC,IAAI;QACFL,SAAS,uCAAuC;YAACQ,KAAKH;YAASI,OAAO;QAAQ;QAC9E,OAAO;IACT,EAAE,OAAM;IACN,mBAAmB;IACrB;IACA,OAAO;AACT;AAEA,SAASG,wBAAwBP,OAAe;IAC9C,IAAI;QACFL,SAAS,mBAAmB;YAACQ,KAAKH;YAASI,OAAO;QAAQ;QAC1D,OAAO;IACT,EAAE,OAAM;IACN,mBAAmB;IACrB;IACA,OAAO;AACT"}
|
|
@@ -194,7 +194,9 @@ export async function applyEnvVariables(root, envData, targetName = '.env') {
|
|
|
194
194
|
await writeFile(join(root, targetName), envContent);
|
|
195
195
|
} catch (err) {
|
|
196
196
|
debug(`Error setting environment variables: ${err}`);
|
|
197
|
-
throw new Error('Failed to set environment variables. This could be due to file permissions or the .env file format. See https://www.sanity.io/docs/environment-variables for details on environment variable setup.'
|
|
197
|
+
throw new Error('Failed to set environment variables. This could be due to file permissions or the .env file format. See https://www.sanity.io/docs/environment-variables for details on environment variable setup.', {
|
|
198
|
+
cause: err
|
|
199
|
+
});
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
export async function tryApplyPackageName(root, name) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/init/remoteTemplate.ts"],"sourcesContent":["import {access, readFile, writeFile} from 'node:fs/promises'\nimport {join, posix, sep} from 'node:path'\nimport {Readable} from 'node:stream'\nimport {pipeline} from 'node:stream/promises'\n\nimport {readPackageJson, subdebug} from '@sanity/cli-core'\nimport {ENV_TEMPLATE_FILES, REQUIRED_ENV_VAR} from '@sanity/template-validator'\nimport {x} from 'tar'\n\nconst debug = subdebug('remoteTemplate')\n\nconst DISALLOWED_PATHS = [\n // Prevent security risks from unknown GitHub Actions\n '/.github/',\n]\n\nconst ENV_VAR = {\n ...REQUIRED_ENV_VAR,\n READ_TOKEN: 'SANITY_API_READ_TOKEN',\n WRITE_TOKEN: 'SANITY_API_WRITE_TOKEN',\n} as const\n\ntype EnvData = {\n dataset: string\n projectId: string\n readToken?: string\n writeToken?: string\n}\n\ntype GitHubUrlString =\n | `https://github.com/${string}/${string}`\n | `https://www.github.com/${string}/${string}`\n\nexport type RepoInfo = {\n branch: string\n filePath: string\n name: string\n username: string\n}\n\nexport function getGitHubRawContentUrl(repoInfo: RepoInfo): string {\n const {branch, filePath, name, username} = repoInfo\n return `https://raw.githubusercontent.com/${username}/${name}/${branch}/${filePath}`\n}\n\nfunction isGitHubRepoShorthand(value: string): boolean {\n if (URL.canParse(value)) {\n return false\n }\n // This supports :owner/:repo and :owner/:repo/nested/path, e.g.\n // sanity-io/sanity\n // sanity-io/sanity/templates/next-js\n // sanity-io/templates/live-content-api\n // sanity-io/sanity/packages/@sanity/cli/test/test-template\n return /^[\\w-]+\\/[\\w-.]+(\\/[@\\w-.]+)*$/.test(value)\n}\n\nfunction isGitHubRepoUrl(value: string | URL): value is GitHubUrlString | URL {\n if (!URL.canParse(value)) {\n return false\n }\n const url = new URL(value)\n const pathSegments = url.pathname.slice(1).split('/')\n\n return (\n url.protocol === 'https:' &&\n url.hostname === 'github.com' &&\n // The pathname must have at least 2 segments. If it has more than 2, the\n // third must be \"tree\" and it must have at least 4 segments.\n // https://github.com/:owner/:repo\n // https://github.com/:owner/:repo/tree/:ref\n pathSegments.length >= 2 &&\n (pathSegments.length > 2 ? pathSegments[2] === 'tree' && pathSegments.length >= 4 : true)\n )\n}\n\nasync function downloadTarStream(url: string, bearerToken?: string): Promise<Readable> {\n const headers: Record<string, string> = {}\n if (bearerToken) {\n headers.Authorization = `Bearer ${bearerToken}`\n }\n\n const res = await fetch(url, {headers})\n\n if (!res.body) {\n throw new Error(`Failed to download: ${url}`)\n }\n\n // eslint-disable-next-line n/no-unsupported-features/node-builtins\n return Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0])\n}\n\nexport function checkIsRemoteTemplate(templateName?: string): boolean {\n return templateName?.includes('/') ?? false\n}\n\nexport async function getGitHubRepoInfo(value: string, bearerToken?: string): Promise<RepoInfo> {\n let username = ''\n let name = ''\n let branch = ''\n let filePath = ''\n\n if (isGitHubRepoShorthand(value)) {\n const parts = value.split('/')\n username = parts[0]\n name = parts[1]\n // If there are more segments after owner/repo, they form the file path\n if (parts.length > 2) {\n filePath = parts.slice(2).join('/')\n }\n }\n\n if (isGitHubRepoUrl(value)) {\n const url = new URL(value)\n const pathSegments = url.pathname.slice(1).split('/')\n username = pathSegments[0]\n name = pathSegments[1]\n\n // If we have a \"tree\" segment, everything after branch is the file path\n if (pathSegments[2] === 'tree') {\n branch = pathSegments[3]\n if (pathSegments.length > 4) {\n filePath = pathSegments.slice(4).join('/')\n }\n }\n }\n\n if (!username || !name) {\n throw new Error('Invalid GitHub repository format')\n }\n\n const tokenMessage =\n 'GitHub repository not found. For private repositories, use --template-token to provide an access token.\\n\\n' +\n 'You can generate a new token at https://github.com/settings/personal-access-tokens/new\\n' +\n 'Set the token to \"read-only\" with repository access and a short expiry (e.g. 7 days) for security.'\n\n try {\n const headers: Record<string, string> = {}\n if (bearerToken) {\n headers.Authorization = `Bearer ${bearerToken}`\n }\n\n const infoResponse = await fetch(`https://api.github.com/repos/${username}/${name}`, {\n headers,\n })\n\n if (infoResponse.status !== 200) {\n if (infoResponse.status === 404) {\n throw new Error(tokenMessage)\n }\n throw new Error('GitHub repository not found')\n }\n\n const info = await infoResponse.json()\n\n return {\n branch: branch || info.default_branch,\n filePath,\n name,\n username,\n }\n } catch {\n throw new Error(tokenMessage)\n }\n}\n\nexport async function downloadAndExtractRepo(\n root: string,\n {branch, filePath, name, username}: RepoInfo,\n bearerToken?: string,\n): Promise<void> {\n let rootPath: string | null = null\n await pipeline(\n await downloadTarStream(\n `https://codeload.github.com/${username}/${name}/tar.gz/${branch}`,\n bearerToken,\n ),\n x({\n cwd: root,\n filter: (p: string) => {\n const posixPath = p.split(sep).join(posix.sep)\n if (rootPath === null) {\n const pathSegments = posixPath.split(posix.sep)\n rootPath = pathSegments.length > 0 ? pathSegments[0] : null\n }\n for (const disallowedPath of DISALLOWED_PATHS) {\n if (posixPath.includes(disallowedPath)) return false\n }\n return posixPath.startsWith(`${rootPath}${filePath ? `/${filePath}/` : '/'}`)\n },\n strip: filePath ? filePath.split('/').length + 1 : 1,\n }),\n )\n}\n\nexport async function checkIfNeedsApiToken(root: string, type: 'read' | 'write'): Promise<boolean> {\n try {\n const templatePath = await Promise.any(\n ENV_TEMPLATE_FILES.map(async (file) => {\n await access(join(root, file))\n return file\n }),\n )\n const templateContent = await readFile(join(root, templatePath), 'utf8')\n return templateContent.includes(type === 'read' ? ENV_VAR.READ_TOKEN : ENV_VAR.WRITE_TOKEN)\n } catch {\n return false\n }\n}\n\nexport async function applyEnvVariables(\n root: string,\n envData: EnvData,\n targetName = '.env',\n): Promise<void> {\n const templatePath = await Promise.any(\n ENV_TEMPLATE_FILES.map(async (file) => {\n await access(join(root, file))\n return file\n }),\n ).catch(() => {})\n\n if (!templatePath) {\n return // No template .env file found, skip\n }\n\n try {\n const templateContent = await readFile(join(root, templatePath), 'utf8')\n const {dataset, projectId, readToken = '', writeToken = ''} = envData\n\n const findAndReplaceVariable = (\n content: string,\n varRegex: RegExp | string,\n value: string,\n useQuotes: boolean,\n ) => {\n const varPattern = typeof varRegex === 'string' ? varRegex : varRegex.source\n const pattern = new RegExp(`.*${varPattern}=.*$`, 'gm')\n const matches = content.matchAll(pattern)\n\n let result = content\n for (const match of matches) {\n if (!match[0]) continue\n const varName = match[0].split('=')[0].trim()\n result = result.replaceAll(\n new RegExp(`${varName}=.*$`, 'gm'),\n `${varName}=${useQuotes ? `\"${value}\"` : value}`,\n )\n }\n\n return result\n }\n\n let envContent = templateContent\n const vars = [\n {pattern: ENV_VAR.PROJECT_ID, value: projectId},\n {pattern: ENV_VAR.DATASET, value: dataset},\n {pattern: ENV_VAR.READ_TOKEN, value: readToken},\n {pattern: ENV_VAR.WRITE_TOKEN, value: writeToken},\n ]\n const useQuotes = templateContent.includes('=\"')\n\n for (const {pattern, value} of vars) {\n envContent = findAndReplaceVariable(envContent, pattern, value, useQuotes)\n }\n\n await writeFile(join(root, targetName), envContent)\n } catch (err) {\n debug(`Error setting environment variables: ${err}`)\n throw new Error(\n 'Failed to set environment variables. This could be due to file permissions or the .env file format. See https://www.sanity.io/docs/environment-variables for details on environment variable setup.',\n )\n }\n}\n\nexport async function tryApplyPackageName(root: string, name: string): Promise<void> {\n try {\n const pkg = await readPackageJson(join(root, 'package.json'))\n pkg.name = name\n\n await writeFile(join(root, 'package.json'), JSON.stringify(pkg, null, 2))\n } catch {\n // noop\n }\n}\n"],"names":["access","readFile","writeFile","join","posix","sep","Readable","pipeline","readPackageJson","subdebug","ENV_TEMPLATE_FILES","REQUIRED_ENV_VAR","x","debug","DISALLOWED_PATHS","ENV_VAR","READ_TOKEN","WRITE_TOKEN","getGitHubRawContentUrl","repoInfo","branch","filePath","name","username","isGitHubRepoShorthand","value","URL","canParse","test","isGitHubRepoUrl","url","pathSegments","pathname","slice","split","protocol","hostname","length","downloadTarStream","bearerToken","headers","Authorization","res","fetch","body","Error","fromWeb","checkIsRemoteTemplate","templateName","includes","getGitHubRepoInfo","parts","tokenMessage","infoResponse","status","info","json","default_branch","downloadAndExtractRepo","root","rootPath","cwd","filter","p","posixPath","disallowedPath","startsWith","strip","checkIfNeedsApiToken","type","templatePath","Promise","any","map","file","templateContent","applyEnvVariables","envData","targetName","catch","dataset","projectId","readToken","writeToken","findAndReplaceVariable","content","varRegex","useQuotes","varPattern","source","pattern","RegExp","matches","matchAll","result","match","varName","trim","replaceAll","envContent","vars","PROJECT_ID","DATASET","err","tryApplyPackageName","pkg","JSON","stringify"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/init/remoteTemplate.ts"],"sourcesContent":["import {access, readFile, writeFile} from 'node:fs/promises'\nimport {join, posix, sep} from 'node:path'\nimport {Readable} from 'node:stream'\nimport {pipeline} from 'node:stream/promises'\n\nimport {readPackageJson, subdebug} from '@sanity/cli-core'\nimport {ENV_TEMPLATE_FILES, REQUIRED_ENV_VAR} from '@sanity/template-validator'\nimport {x} from 'tar'\n\nconst debug = subdebug('remoteTemplate')\n\nconst DISALLOWED_PATHS = [\n // Prevent security risks from unknown GitHub Actions\n '/.github/',\n]\n\nconst ENV_VAR = {\n ...REQUIRED_ENV_VAR,\n READ_TOKEN: 'SANITY_API_READ_TOKEN',\n WRITE_TOKEN: 'SANITY_API_WRITE_TOKEN',\n} as const\n\ntype EnvData = {\n dataset: string\n projectId: string\n readToken?: string\n writeToken?: string\n}\n\ntype GitHubUrlString =\n | `https://github.com/${string}/${string}`\n | `https://www.github.com/${string}/${string}`\n\nexport type RepoInfo = {\n branch: string\n filePath: string\n name: string\n username: string\n}\n\nexport function getGitHubRawContentUrl(repoInfo: RepoInfo): string {\n const {branch, filePath, name, username} = repoInfo\n return `https://raw.githubusercontent.com/${username}/${name}/${branch}/${filePath}`\n}\n\nfunction isGitHubRepoShorthand(value: string): boolean {\n if (URL.canParse(value)) {\n return false\n }\n // This supports :owner/:repo and :owner/:repo/nested/path, e.g.\n // sanity-io/sanity\n // sanity-io/sanity/templates/next-js\n // sanity-io/templates/live-content-api\n // sanity-io/sanity/packages/@sanity/cli/test/test-template\n return /^[\\w-]+\\/[\\w-.]+(\\/[@\\w-.]+)*$/.test(value)\n}\n\nfunction isGitHubRepoUrl(value: string | URL): value is GitHubUrlString | URL {\n if (!URL.canParse(value)) {\n return false\n }\n const url = new URL(value)\n const pathSegments = url.pathname.slice(1).split('/')\n\n return (\n url.protocol === 'https:' &&\n url.hostname === 'github.com' &&\n // The pathname must have at least 2 segments. If it has more than 2, the\n // third must be \"tree\" and it must have at least 4 segments.\n // https://github.com/:owner/:repo\n // https://github.com/:owner/:repo/tree/:ref\n pathSegments.length >= 2 &&\n (pathSegments.length > 2 ? pathSegments[2] === 'tree' && pathSegments.length >= 4 : true)\n )\n}\n\nasync function downloadTarStream(url: string, bearerToken?: string): Promise<Readable> {\n const headers: Record<string, string> = {}\n if (bearerToken) {\n headers.Authorization = `Bearer ${bearerToken}`\n }\n\n const res = await fetch(url, {headers})\n\n if (!res.body) {\n throw new Error(`Failed to download: ${url}`)\n }\n\n // eslint-disable-next-line n/no-unsupported-features/node-builtins\n return Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0])\n}\n\nexport function checkIsRemoteTemplate(templateName?: string): boolean {\n return templateName?.includes('/') ?? false\n}\n\nexport async function getGitHubRepoInfo(value: string, bearerToken?: string): Promise<RepoInfo> {\n let username = ''\n let name = ''\n let branch = ''\n let filePath = ''\n\n if (isGitHubRepoShorthand(value)) {\n const parts = value.split('/')\n username = parts[0]\n name = parts[1]\n // If there are more segments after owner/repo, they form the file path\n if (parts.length > 2) {\n filePath = parts.slice(2).join('/')\n }\n }\n\n if (isGitHubRepoUrl(value)) {\n const url = new URL(value)\n const pathSegments = url.pathname.slice(1).split('/')\n username = pathSegments[0]\n name = pathSegments[1]\n\n // If we have a \"tree\" segment, everything after branch is the file path\n if (pathSegments[2] === 'tree') {\n branch = pathSegments[3]\n if (pathSegments.length > 4) {\n filePath = pathSegments.slice(4).join('/')\n }\n }\n }\n\n if (!username || !name) {\n throw new Error('Invalid GitHub repository format')\n }\n\n const tokenMessage =\n 'GitHub repository not found. For private repositories, use --template-token to provide an access token.\\n\\n' +\n 'You can generate a new token at https://github.com/settings/personal-access-tokens/new\\n' +\n 'Set the token to \"read-only\" with repository access and a short expiry (e.g. 7 days) for security.'\n\n try {\n const headers: Record<string, string> = {}\n if (bearerToken) {\n headers.Authorization = `Bearer ${bearerToken}`\n }\n\n const infoResponse = await fetch(`https://api.github.com/repos/${username}/${name}`, {\n headers,\n })\n\n if (infoResponse.status !== 200) {\n if (infoResponse.status === 404) {\n throw new Error(tokenMessage)\n }\n throw new Error('GitHub repository not found')\n }\n\n const info = await infoResponse.json()\n\n return {\n branch: branch || info.default_branch,\n filePath,\n name,\n username,\n }\n } catch {\n throw new Error(tokenMessage)\n }\n}\n\nexport async function downloadAndExtractRepo(\n root: string,\n {branch, filePath, name, username}: RepoInfo,\n bearerToken?: string,\n): Promise<void> {\n let rootPath: string | null = null\n await pipeline(\n await downloadTarStream(\n `https://codeload.github.com/${username}/${name}/tar.gz/${branch}`,\n bearerToken,\n ),\n x({\n cwd: root,\n filter: (p: string) => {\n const posixPath = p.split(sep).join(posix.sep)\n if (rootPath === null) {\n const pathSegments = posixPath.split(posix.sep)\n rootPath = pathSegments.length > 0 ? pathSegments[0] : null\n }\n for (const disallowedPath of DISALLOWED_PATHS) {\n if (posixPath.includes(disallowedPath)) return false\n }\n return posixPath.startsWith(`${rootPath}${filePath ? `/${filePath}/` : '/'}`)\n },\n strip: filePath ? filePath.split('/').length + 1 : 1,\n }),\n )\n}\n\nexport async function checkIfNeedsApiToken(root: string, type: 'read' | 'write'): Promise<boolean> {\n try {\n const templatePath = await Promise.any(\n ENV_TEMPLATE_FILES.map(async (file) => {\n await access(join(root, file))\n return file\n }),\n )\n const templateContent = await readFile(join(root, templatePath), 'utf8')\n return templateContent.includes(type === 'read' ? ENV_VAR.READ_TOKEN : ENV_VAR.WRITE_TOKEN)\n } catch {\n return false\n }\n}\n\nexport async function applyEnvVariables(\n root: string,\n envData: EnvData,\n targetName = '.env',\n): Promise<void> {\n const templatePath = await Promise.any(\n ENV_TEMPLATE_FILES.map(async (file) => {\n await access(join(root, file))\n return file\n }),\n ).catch(() => {})\n\n if (!templatePath) {\n return // No template .env file found, skip\n }\n\n try {\n const templateContent = await readFile(join(root, templatePath), 'utf8')\n const {dataset, projectId, readToken = '', writeToken = ''} = envData\n\n const findAndReplaceVariable = (\n content: string,\n varRegex: RegExp | string,\n value: string,\n useQuotes: boolean,\n ) => {\n const varPattern = typeof varRegex === 'string' ? varRegex : varRegex.source\n const pattern = new RegExp(`.*${varPattern}=.*$`, 'gm')\n const matches = content.matchAll(pattern)\n\n let result = content\n for (const match of matches) {\n if (!match[0]) continue\n const varName = match[0].split('=')[0].trim()\n result = result.replaceAll(\n new RegExp(`${varName}=.*$`, 'gm'),\n `${varName}=${useQuotes ? `\"${value}\"` : value}`,\n )\n }\n\n return result\n }\n\n let envContent = templateContent\n const vars = [\n {pattern: ENV_VAR.PROJECT_ID, value: projectId},\n {pattern: ENV_VAR.DATASET, value: dataset},\n {pattern: ENV_VAR.READ_TOKEN, value: readToken},\n {pattern: ENV_VAR.WRITE_TOKEN, value: writeToken},\n ]\n const useQuotes = templateContent.includes('=\"')\n\n for (const {pattern, value} of vars) {\n envContent = findAndReplaceVariable(envContent, pattern, value, useQuotes)\n }\n\n await writeFile(join(root, targetName), envContent)\n } catch (err) {\n debug(`Error setting environment variables: ${err}`)\n throw new Error(\n 'Failed to set environment variables. This could be due to file permissions or the .env file format. See https://www.sanity.io/docs/environment-variables for details on environment variable setup.',\n {cause: err},\n )\n }\n}\n\nexport async function tryApplyPackageName(root: string, name: string): Promise<void> {\n try {\n const pkg = await readPackageJson(join(root, 'package.json'))\n pkg.name = name\n\n await writeFile(join(root, 'package.json'), JSON.stringify(pkg, null, 2))\n } catch {\n // noop\n }\n}\n"],"names":["access","readFile","writeFile","join","posix","sep","Readable","pipeline","readPackageJson","subdebug","ENV_TEMPLATE_FILES","REQUIRED_ENV_VAR","x","debug","DISALLOWED_PATHS","ENV_VAR","READ_TOKEN","WRITE_TOKEN","getGitHubRawContentUrl","repoInfo","branch","filePath","name","username","isGitHubRepoShorthand","value","URL","canParse","test","isGitHubRepoUrl","url","pathSegments","pathname","slice","split","protocol","hostname","length","downloadTarStream","bearerToken","headers","Authorization","res","fetch","body","Error","fromWeb","checkIsRemoteTemplate","templateName","includes","getGitHubRepoInfo","parts","tokenMessage","infoResponse","status","info","json","default_branch","downloadAndExtractRepo","root","rootPath","cwd","filter","p","posixPath","disallowedPath","startsWith","strip","checkIfNeedsApiToken","type","templatePath","Promise","any","map","file","templateContent","applyEnvVariables","envData","targetName","catch","dataset","projectId","readToken","writeToken","findAndReplaceVariable","content","varRegex","useQuotes","varPattern","source","pattern","RegExp","matches","matchAll","result","match","varName","trim","replaceAll","envContent","vars","PROJECT_ID","DATASET","err","cause","tryApplyPackageName","pkg","JSON","stringify"],"mappings":"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"}
|
|
@@ -163,7 +163,7 @@ export const { sanityFetch, SanityLive } = defineLive({
|
|
|
163
163
|
});
|
|
164
164
|
`;
|
|
165
165
|
const imageTS = `import createImageUrlBuilder from '@sanity/image-url'
|
|
166
|
-
import { SanityImageSource } from "@sanity/image-url
|
|
166
|
+
import type { SanityImageSource } from "@sanity/image-url";
|
|
167
167
|
|
|
168
168
|
import { dataset, projectId } from '../env'
|
|
169
169
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/actions/init/templates/nextjs/index.ts"],"sourcesContent":["import {blogSchemaFolder, blogSchemaJS, blogSchemaTS} from './schemaTypes/blog.js'\n\nexport const sanityConfigTemplate = (hasSrcFolder = false): string => `'use client'\n\n/**\n * This configuration is used to for the Sanity Studio that’s mounted on the \\`:route:\\` route\n */\n\nimport {visionTool} from '@sanity/vision'\nimport {defineConfig} from 'sanity'\nimport {structureTool} from 'sanity/structure'\n\n// Go to https://www.sanity.io/docs/api-versioning to learn how API versioning works\nimport {apiVersion, dataset, projectId} from ${hasSrcFolder ? \"'./src/sanity/env'\" : \"'./sanity/env'\"}\nimport {schema} from ${hasSrcFolder ? \"'./src/sanity/schemaTypes'\" : \"'./sanity/schemaTypes'\"}\nimport {structure} from ${hasSrcFolder ? \"'./src/sanity/structure'\" : \"'./sanity/structure'\"}\n\nexport default defineConfig({\n basePath: ':basePath:',\n projectId,\n dataset,\n // Add and edit the content schema in the './sanity/schemaTypes' folder\n schema,\n plugins: [\n structureTool({structure}),\n // Vision is for querying with GROQ from inside the Studio\n // https://www.sanity.io/docs/the-vision-plugin\n visionTool({defaultApiVersion: apiVersion}),\n ],\n})\n`\n\nexport const sanityCliTemplate = `/**\n* This configuration file lets you run \\`$ sanity [command]\\` in this folder\n* Go to https://www.sanity.io/docs/cli to learn more.\n**/\nimport { defineCliConfig } from 'sanity/cli'\n\nconst projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID\nconst dataset = process.env.NEXT_PUBLIC_SANITY_DATASET\n\nexport default defineCliConfig({ api: { projectId, dataset } })\n`\n\nexport const sanityStudioTemplate = `/**\n * This route is responsible for the built-in authoring environment using Sanity Studio.\n * All routes under your studio path is handled by this file using Next.js' catch-all routes:\n * https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes\n *\n * You can learn more about the next-sanity package here:\n * https://github.com/sanity-io/next-sanity\n */\n\nimport { NextStudio } from 'next-sanity/studio'\nimport config from ':configPath:'\n\nexport const dynamic = 'force-static'\n\nexport { metadata, viewport } from 'next-sanity/studio'\n\nexport default function StudioPage() {\n return <NextStudio config={config} />\n}\n`\n\n// Format today's date like YYYY-MM-DD\nconst envTS = `export const apiVersion =\n process.env.NEXT_PUBLIC_SANITY_API_VERSION || '${new Date().toISOString().split('T')[0]}'\n\nexport const dataset = assertValue(\n process.env.NEXT_PUBLIC_SANITY_DATASET,\n 'Missing environment variable: NEXT_PUBLIC_SANITY_DATASET'\n)\n\nexport const projectId = assertValue(\n process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,\n 'Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID'\n)\n\nfunction assertValue<T>(v: T | undefined, errorMessage: string): T {\n if (v === undefined) {\n throw new Error(errorMessage)\n }\n\n return v\n}\n`\n\nconst envJS = `export const apiVersion =\n process.env.NEXT_PUBLIC_SANITY_API_VERSION || '${new Date().toISOString().split('T')[0]}'\n\nexport const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET;\nexport const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID;\n`\n\nconst schemaTS = `import { type SchemaTypeDefinition } from 'sanity'\n\nexport const schema: { types: SchemaTypeDefinition[] } = {\n types: [],\n}\n`\n\nconst schemaJS = `export const schema = {\n types: [],\n}\n`\n\nconst blogStructureTS = `import type {StructureResolver} from 'sanity/structure'\n\n// https://www.sanity.io/docs/structure-builder-cheat-sheet\nexport const structure: StructureResolver = (S) =>\n S.list()\n .title('Blog')\n .items([\n S.documentTypeListItem('post').title('Posts'),\n S.documentTypeListItem('category').title('Categories'),\n S.documentTypeListItem('author').title('Authors'),\n S.divider(),\n ...S.documentTypeListItems().filter(\n (item) => item.getId() && !['post', 'category', 'author'].includes(item.getId()!),\n ),\n ])\n`\n\nconst blogStructureJS = `// https://www.sanity.io/docs/structure-builder-cheat-sheet\nexport const structure = (S) =>\n S.list()\n .title('Blog')\n .items([\n S.documentTypeListItem('post').title('Posts'),\n S.documentTypeListItem('category').title('Categories'),\n S.documentTypeListItem('author').title('Authors'),\n S.divider(),\n ...S.documentTypeListItems().filter(\n (item) => item.getId() && !['post', 'category', 'author'].includes(item.getId()),\n ),\n ])\n`\n\nconst structureTS = `import type {StructureResolver} from 'sanity/structure'\n\n// https://www.sanity.io/docs/structure-builder-cheat-sheet\nexport const structure: StructureResolver = (S) =>\n S.list()\n .title('Content')\n .items(S.documentTypeListItems())\n`\n\nconst structureJS = `// https://www.sanity.io/docs/structure-builder-cheat-sheet\nexport const structure = (S) =>\n S.list()\n .title('Content')\n .items(S.documentTypeListItems())\n`\n\nconst client = `import { createClient } from 'next-sanity'\n\nimport { apiVersion, dataset, projectId } from '../env'\n\nexport const client = createClient({\n projectId,\n dataset,\n apiVersion,\n useCdn: true, // Set to false if statically generating pages, using ISR or tag-based revalidation\n})\n`\n\nconst live = `// Querying with \"sanityFetch\" will keep content automatically updated\n// Before using it, import and render \"<SanityLive />\" in your layout, see\n// https://github.com/sanity-io/next-sanity#live-content-api for more information.\nimport { defineLive } from \"next-sanity/live\";\nimport { client } from './client'\n\nexport const { sanityFetch, SanityLive } = defineLive({\n client,\n});\n`\n\nconst imageTS = `import createImageUrlBuilder from '@sanity/image-url'\nimport { SanityImageSource } from \"@sanity/image-url
|
|
1
|
+
{"version":3,"sources":["../../../../../src/actions/init/templates/nextjs/index.ts"],"sourcesContent":["import {blogSchemaFolder, blogSchemaJS, blogSchemaTS} from './schemaTypes/blog.js'\n\nexport const sanityConfigTemplate = (hasSrcFolder = false): string => `'use client'\n\n/**\n * This configuration is used to for the Sanity Studio that’s mounted on the \\`:route:\\` route\n */\n\nimport {visionTool} from '@sanity/vision'\nimport {defineConfig} from 'sanity'\nimport {structureTool} from 'sanity/structure'\n\n// Go to https://www.sanity.io/docs/api-versioning to learn how API versioning works\nimport {apiVersion, dataset, projectId} from ${hasSrcFolder ? \"'./src/sanity/env'\" : \"'./sanity/env'\"}\nimport {schema} from ${hasSrcFolder ? \"'./src/sanity/schemaTypes'\" : \"'./sanity/schemaTypes'\"}\nimport {structure} from ${hasSrcFolder ? \"'./src/sanity/structure'\" : \"'./sanity/structure'\"}\n\nexport default defineConfig({\n basePath: ':basePath:',\n projectId,\n dataset,\n // Add and edit the content schema in the './sanity/schemaTypes' folder\n schema,\n plugins: [\n structureTool({structure}),\n // Vision is for querying with GROQ from inside the Studio\n // https://www.sanity.io/docs/the-vision-plugin\n visionTool({defaultApiVersion: apiVersion}),\n ],\n})\n`\n\nexport const sanityCliTemplate = `/**\n* This configuration file lets you run \\`$ sanity [command]\\` in this folder\n* Go to https://www.sanity.io/docs/cli to learn more.\n**/\nimport { defineCliConfig } from 'sanity/cli'\n\nconst projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID\nconst dataset = process.env.NEXT_PUBLIC_SANITY_DATASET\n\nexport default defineCliConfig({ api: { projectId, dataset } })\n`\n\nexport const sanityStudioTemplate = `/**\n * This route is responsible for the built-in authoring environment using Sanity Studio.\n * All routes under your studio path is handled by this file using Next.js' catch-all routes:\n * https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes\n *\n * You can learn more about the next-sanity package here:\n * https://github.com/sanity-io/next-sanity\n */\n\nimport { NextStudio } from 'next-sanity/studio'\nimport config from ':configPath:'\n\nexport const dynamic = 'force-static'\n\nexport { metadata, viewport } from 'next-sanity/studio'\n\nexport default function StudioPage() {\n return <NextStudio config={config} />\n}\n`\n\n// Format today's date like YYYY-MM-DD\nconst envTS = `export const apiVersion =\n process.env.NEXT_PUBLIC_SANITY_API_VERSION || '${new Date().toISOString().split('T')[0]}'\n\nexport const dataset = assertValue(\n process.env.NEXT_PUBLIC_SANITY_DATASET,\n 'Missing environment variable: NEXT_PUBLIC_SANITY_DATASET'\n)\n\nexport const projectId = assertValue(\n process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,\n 'Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID'\n)\n\nfunction assertValue<T>(v: T | undefined, errorMessage: string): T {\n if (v === undefined) {\n throw new Error(errorMessage)\n }\n\n return v\n}\n`\n\nconst envJS = `export const apiVersion =\n process.env.NEXT_PUBLIC_SANITY_API_VERSION || '${new Date().toISOString().split('T')[0]}'\n\nexport const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET;\nexport const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID;\n`\n\nconst schemaTS = `import { type SchemaTypeDefinition } from 'sanity'\n\nexport const schema: { types: SchemaTypeDefinition[] } = {\n types: [],\n}\n`\n\nconst schemaJS = `export const schema = {\n types: [],\n}\n`\n\nconst blogStructureTS = `import type {StructureResolver} from 'sanity/structure'\n\n// https://www.sanity.io/docs/structure-builder-cheat-sheet\nexport const structure: StructureResolver = (S) =>\n S.list()\n .title('Blog')\n .items([\n S.documentTypeListItem('post').title('Posts'),\n S.documentTypeListItem('category').title('Categories'),\n S.documentTypeListItem('author').title('Authors'),\n S.divider(),\n ...S.documentTypeListItems().filter(\n (item) => item.getId() && !['post', 'category', 'author'].includes(item.getId()!),\n ),\n ])\n`\n\nconst blogStructureJS = `// https://www.sanity.io/docs/structure-builder-cheat-sheet\nexport const structure = (S) =>\n S.list()\n .title('Blog')\n .items([\n S.documentTypeListItem('post').title('Posts'),\n S.documentTypeListItem('category').title('Categories'),\n S.documentTypeListItem('author').title('Authors'),\n S.divider(),\n ...S.documentTypeListItems().filter(\n (item) => item.getId() && !['post', 'category', 'author'].includes(item.getId()),\n ),\n ])\n`\n\nconst structureTS = `import type {StructureResolver} from 'sanity/structure'\n\n// https://www.sanity.io/docs/structure-builder-cheat-sheet\nexport const structure: StructureResolver = (S) =>\n S.list()\n .title('Content')\n .items(S.documentTypeListItems())\n`\n\nconst structureJS = `// https://www.sanity.io/docs/structure-builder-cheat-sheet\nexport const structure = (S) =>\n S.list()\n .title('Content')\n .items(S.documentTypeListItems())\n`\n\nconst client = `import { createClient } from 'next-sanity'\n\nimport { apiVersion, dataset, projectId } from '../env'\n\nexport const client = createClient({\n projectId,\n dataset,\n apiVersion,\n useCdn: true, // Set to false if statically generating pages, using ISR or tag-based revalidation\n})\n`\n\nconst live = `// Querying with \"sanityFetch\" will keep content automatically updated\n// Before using it, import and render \"<SanityLive />\" in your layout, see\n// https://github.com/sanity-io/next-sanity#live-content-api for more information.\nimport { defineLive } from \"next-sanity/live\";\nimport { client } from './client'\n\nexport const { sanityFetch, SanityLive } = defineLive({\n client,\n});\n`\n\nconst imageTS = `import createImageUrlBuilder from '@sanity/image-url'\nimport type { SanityImageSource } from \"@sanity/image-url\";\n\nimport { dataset, projectId } from '../env'\n\n// https://www.sanity.io/docs/image-url\nconst builder = createImageUrlBuilder({ projectId, dataset })\n\nexport const urlFor = (source: SanityImageSource) => {\n return builder.image(source)\n}\n`\n\nconst imageJS = `import createImageUrlBuilder from '@sanity/image-url'\n\nimport { dataset, projectId } from '../env'\n\n// https://www.sanity.io/docs/image-url\nconst builder = createImageUrlBuilder({ projectId, dataset })\n\nexport const urlFor = (source) => {\n return builder.image(source)\n}\n`\n\ntype FolderStructure = Record<string, Record<string, string> | string>\n\nexport const sanityFolder = (\n useTypeScript: boolean,\n template?: 'blog' | 'clean',\n): FolderStructure => {\n // Files used in both templates\n const structure: FolderStructure = {\n 'env.': useTypeScript ? envTS : envJS,\n lib: {\n 'client.': client,\n 'image.': useTypeScript ? imageTS : imageJS,\n 'live.': live,\n },\n }\n\n if (template === 'blog') {\n structure.schemaTypes = {\n ...blogSchemaFolder,\n 'index.': useTypeScript ? blogSchemaTS : blogSchemaJS,\n }\n structure['structure.'] = useTypeScript ? blogStructureTS : blogStructureJS\n } else {\n structure.schemaTypes = {\n 'index.': useTypeScript ? schemaTS : schemaJS,\n }\n structure['structure.'] = useTypeScript ? structureTS : structureJS\n }\n\n return structure\n}\n"],"names":["blogSchemaFolder","blogSchemaJS","blogSchemaTS","sanityConfigTemplate","hasSrcFolder","sanityCliTemplate","sanityStudioTemplate","envTS","Date","toISOString","split","envJS","schemaTS","schemaJS","blogStructureTS","blogStructureJS","structureTS","structureJS","client","live","imageTS","imageJS","sanityFolder","useTypeScript","template","structure","lib","schemaTypes"],"mappings":"AAAA,SAAQA,gBAAgB,EAAEC,YAAY,EAAEC,YAAY,QAAO,wBAAuB;AAElF,OAAO,MAAMC,uBAAuB,CAACC,eAAe,KAAK,GAAa,CAAC;;;;;;;;;;;6CAW1B,EAAEA,eAAe,uBAAuB,iBAAiB;qBACjF,EAAEA,eAAe,+BAA+B,yBAAyB;wBACtE,EAAEA,eAAe,6BAA6B,uBAAuB;;;;;;;;;;;;;;;AAe7F,CAAC,CAAA;AAED,OAAO,MAAMC,oBAAoB,CAAC;;;;;;;;;;AAUlC,CAAC,CAAA;AAED,OAAO,MAAMC,uBAAuB,CAAC;;;;;;;;;;;;;;;;;;;AAmBrC,CAAC,CAAA;AAED,sCAAsC;AACtC,MAAMC,QAAQ,CAAC;iDACkC,EAAE,IAAIC,OAAOC,WAAW,GAAGC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;;;;;;;;;;;;;;;;;;;AAmB1F,CAAC;AAED,MAAMC,QAAQ,CAAC;iDACkC,EAAE,IAAIH,OAAOC,WAAW,GAAGC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;;;;AAI1F,CAAC;AAED,MAAME,WAAW,CAAC;;;;;AAKlB,CAAC;AAED,MAAMC,WAAW,CAAC;;;AAGlB,CAAC;AAED,MAAMC,kBAAkB,CAAC;;;;;;;;;;;;;;;AAezB,CAAC;AAED,MAAMC,kBAAkB,CAAC;;;;;;;;;;;;;AAazB,CAAC;AAED,MAAMC,cAAc,CAAC;;;;;;;AAOrB,CAAC;AAED,MAAMC,cAAc,CAAC;;;;;AAKrB,CAAC;AAED,MAAMC,SAAS,CAAC;;;;;;;;;;AAUhB,CAAC;AAED,MAAMC,OAAO,CAAC;;;;;;;;;AASd,CAAC;AAED,MAAMC,UAAU,CAAC;;;;;;;;;;;AAWjB,CAAC;AAED,MAAMC,UAAU,CAAC;;;;;;;;;;AAUjB,CAAC;AAID,OAAO,MAAMC,eAAe,CAC1BC,eACAC;IAEA,+BAA+B;IAC/B,MAAMC,YAA6B;QACjC,QAAQF,gBAAgBhB,QAAQI;QAChCe,KAAK;YACH,WAAWR;YACX,UAAUK,gBAAgBH,UAAUC;YACpC,SAASF;QACX;IACF;IAEA,IAAIK,aAAa,QAAQ;QACvBC,UAAUE,WAAW,GAAG;YACtB,GAAG3B,gBAAgB;YACnB,UAAUuB,gBAAgBrB,eAAeD;QAC3C;QACAwB,SAAS,CAAC,aAAa,GAAGF,gBAAgBT,kBAAkBC;IAC9D,OAAO;QACLU,UAAUE,WAAW,GAAG;YACtB,UAAUJ,gBAAgBX,WAAWC;QACvC;QACAY,SAAS,CAAC,aAAa,GAAGF,gBAAgBP,cAAcC;IAC1D;IAEA,OAAOQ;AACT,EAAC"}
|
|
@@ -18,7 +18,9 @@ import { getErrorMessage } from '../../util/getErrorMessage.js';
|
|
|
18
18
|
content = await readFile(resolvedPath, 'utf8');
|
|
19
19
|
} catch (err) {
|
|
20
20
|
const message = err instanceof Error ? err.message : String(err);
|
|
21
|
-
throw new Error(`Could not read icon file at "${iconPath}" (resolved: ${resolvedPath}): ${message}
|
|
21
|
+
throw new Error(`Could not read icon file at "${iconPath}" (resolved: ${resolvedPath}): ${message}`, {
|
|
22
|
+
cause: err
|
|
23
|
+
});
|
|
22
24
|
}
|
|
23
25
|
const trimmed = content.trim();
|
|
24
26
|
if (!/<svg[\s>]/i.test(trimmed)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/manifest/extractAppManifest.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport {relative, resolve} from 'node:path'\n\nimport {getCliConfig} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\n\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {type AppManifest} from './types.js'\n\ninterface ExtractAppManifestOptions {\n workDir: string\n}\n\n/**\n * Resolves app.icon from config (a file path) to an SVG string for the manifest.\n * The manifest expects the SVG string inline, not a path.\n * Brett sanitizes SVGs so it's skipped here.\n */\nasync function readIconFromPath(workDir: string, iconPath: string): Promise<string> {\n const resolvedPath = resolve(workDir, iconPath)\n const pathRelativeToWorkDir = relative(workDir, resolvedPath)\n if (pathRelativeToWorkDir.startsWith('..')) {\n throw new Error(\n `Icon path \"${iconPath}\" resolves outside the project directory and is not allowed.`,\n )\n }\n\n let content: string\n try {\n content = await readFile(resolvedPath, 'utf8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(\n `Could not read icon file at \"${iconPath}\" (resolved: ${resolvedPath}): ${message}`,\n )\n }\n\n const trimmed = content.trim()\n if (!/<svg[\\s>]/i.test(trimmed)) {\n throw new Error(\n `Icon file at \"${iconPath}\" does not contain an SVG element. App manifest icons must be SVG files.`,\n )\n }\n\n return trimmed\n}\n\n/**\n *\n * This functions slightly differently from the studio manifest extraction function.\n * We don't need to parse very complicated information like schemas and tools.\n * The app icon in config is a file path (e.g. relative to project root); its content is read and inlined in the manifest.\n */\nexport async function extractAppManifest(\n options: ExtractAppManifestOptions,\n): Promise<AppManifest | undefined> {\n const {workDir} = options\n const {app} = await getCliConfig(workDir)\n if (!app) {\n return undefined\n }\n\n const spin = spinner('Extracting manifest').start()\n\n try {\n let icon: string | undefined\n if (app.icon) {\n icon = await readIconFromPath(workDir, app.icon)\n }\n\n if (!icon && !app.title) {\n spin.succeed('Manifest creation skipped: no icon or title found in app configuration')\n return undefined\n }\n\n const manifest: AppManifest = {\n version: '1',\n ...(icon ? {icon} : {}),\n ...(app.title ? {title: app.title} : {}),\n }\n\n spin.succeed(`Extracted manifest`)\n\n return manifest\n } catch (err) {\n const message = getErrorMessage(err)\n spin.fail(message)\n throw err\n }\n}\n"],"names":["readFile","relative","resolve","getCliConfig","spinner","getErrorMessage","readIconFromPath","workDir","iconPath","resolvedPath","pathRelativeToWorkDir","startsWith","Error","content","err","message","String","trimmed","trim","test","extractAppManifest","options","app","undefined","spin","start","icon","title","succeed","manifest","version","fail"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AACzC,SAAQC,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAE3C,SAAQC,YAAY,QAAO,mBAAkB;AAC7C,SAAQC,OAAO,QAAO,sBAAqB;AAE3C,SAAQC,eAAe,QAAO,gCAA+B;AAO7D;;;;CAIC,GACD,eAAeC,iBAAiBC,OAAe,EAAEC,QAAgB;IAC/D,MAAMC,eAAeP,QAAQK,SAASC;IACtC,MAAME,wBAAwBT,SAASM,SAASE;IAChD,IAAIC,sBAAsBC,UAAU,CAAC,OAAO;QAC1C,MAAM,IAAIC,MACR,CAAC,WAAW,EAAEJ,SAAS,4DAA4D,CAAC;IAExF;IAEA,IAAIK;IACJ,IAAI;QACFA,UAAU,MAAMb,SAASS,cAAc;IACzC,EAAE,OAAOK,KAAK;QACZ,MAAMC,UAAUD,eAAeF,QAAQE,IAAIC,OAAO,GAAGC,OAAOF;QAC5D,MAAM,IAAIF,MACR,CAAC,6BAA6B,EAAEJ,SAAS,aAAa,EAAEC,aAAa,GAAG,EAAEM,SAAS;
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/manifest/extractAppManifest.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport {relative, resolve} from 'node:path'\n\nimport {getCliConfig} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\n\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {type AppManifest} from './types.js'\n\ninterface ExtractAppManifestOptions {\n workDir: string\n}\n\n/**\n * Resolves app.icon from config (a file path) to an SVG string for the manifest.\n * The manifest expects the SVG string inline, not a path.\n * Brett sanitizes SVGs so it's skipped here.\n */\nasync function readIconFromPath(workDir: string, iconPath: string): Promise<string> {\n const resolvedPath = resolve(workDir, iconPath)\n const pathRelativeToWorkDir = relative(workDir, resolvedPath)\n if (pathRelativeToWorkDir.startsWith('..')) {\n throw new Error(\n `Icon path \"${iconPath}\" resolves outside the project directory and is not allowed.`,\n )\n }\n\n let content: string\n try {\n content = await readFile(resolvedPath, 'utf8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(\n `Could not read icon file at \"${iconPath}\" (resolved: ${resolvedPath}): ${message}`,\n {cause: err},\n )\n }\n\n const trimmed = content.trim()\n if (!/<svg[\\s>]/i.test(trimmed)) {\n throw new Error(\n `Icon file at \"${iconPath}\" does not contain an SVG element. App manifest icons must be SVG files.`,\n )\n }\n\n return trimmed\n}\n\n/**\n *\n * This functions slightly differently from the studio manifest extraction function.\n * We don't need to parse very complicated information like schemas and tools.\n * The app icon in config is a file path (e.g. relative to project root); its content is read and inlined in the manifest.\n */\nexport async function extractAppManifest(\n options: ExtractAppManifestOptions,\n): Promise<AppManifest | undefined> {\n const {workDir} = options\n const {app} = await getCliConfig(workDir)\n if (!app) {\n return undefined\n }\n\n const spin = spinner('Extracting manifest').start()\n\n try {\n let icon: string | undefined\n if (app.icon) {\n icon = await readIconFromPath(workDir, app.icon)\n }\n\n if (!icon && !app.title) {\n spin.succeed('Manifest creation skipped: no icon or title found in app configuration')\n return undefined\n }\n\n const manifest: AppManifest = {\n version: '1',\n ...(icon ? {icon} : {}),\n ...(app.title ? {title: app.title} : {}),\n }\n\n spin.succeed(`Extracted manifest`)\n\n return manifest\n } catch (err) {\n const message = getErrorMessage(err)\n spin.fail(message)\n throw err\n }\n}\n"],"names":["readFile","relative","resolve","getCliConfig","spinner","getErrorMessage","readIconFromPath","workDir","iconPath","resolvedPath","pathRelativeToWorkDir","startsWith","Error","content","err","message","String","cause","trimmed","trim","test","extractAppManifest","options","app","undefined","spin","start","icon","title","succeed","manifest","version","fail"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AACzC,SAAQC,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAE3C,SAAQC,YAAY,QAAO,mBAAkB;AAC7C,SAAQC,OAAO,QAAO,sBAAqB;AAE3C,SAAQC,eAAe,QAAO,gCAA+B;AAO7D;;;;CAIC,GACD,eAAeC,iBAAiBC,OAAe,EAAEC,QAAgB;IAC/D,MAAMC,eAAeP,QAAQK,SAASC;IACtC,MAAME,wBAAwBT,SAASM,SAASE;IAChD,IAAIC,sBAAsBC,UAAU,CAAC,OAAO;QAC1C,MAAM,IAAIC,MACR,CAAC,WAAW,EAAEJ,SAAS,4DAA4D,CAAC;IAExF;IAEA,IAAIK;IACJ,IAAI;QACFA,UAAU,MAAMb,SAASS,cAAc;IACzC,EAAE,OAAOK,KAAK;QACZ,MAAMC,UAAUD,eAAeF,QAAQE,IAAIC,OAAO,GAAGC,OAAOF;QAC5D,MAAM,IAAIF,MACR,CAAC,6BAA6B,EAAEJ,SAAS,aAAa,EAAEC,aAAa,GAAG,EAAEM,SAAS,EACnF;YAACE,OAAOH;QAAG;IAEf;IAEA,MAAMI,UAAUL,QAAQM,IAAI;IAC5B,IAAI,CAAC,aAAaC,IAAI,CAACF,UAAU;QAC/B,MAAM,IAAIN,MACR,CAAC,cAAc,EAAEJ,SAAS,wEAAwE,CAAC;IAEvG;IAEA,OAAOU;AACT;AAEA;;;;;CAKC,GACD,OAAO,eAAeG,mBACpBC,OAAkC;IAElC,MAAM,EAACf,OAAO,EAAC,GAAGe;IAClB,MAAM,EAACC,GAAG,EAAC,GAAG,MAAMpB,aAAaI;IACjC,IAAI,CAACgB,KAAK;QACR,OAAOC;IACT;IAEA,MAAMC,OAAOrB,QAAQ,uBAAuBsB,KAAK;IAEjD,IAAI;QACF,IAAIC;QACJ,IAAIJ,IAAII,IAAI,EAAE;YACZA,OAAO,MAAMrB,iBAAiBC,SAASgB,IAAII,IAAI;QACjD;QAEA,IAAI,CAACA,QAAQ,CAACJ,IAAIK,KAAK,EAAE;YACvBH,KAAKI,OAAO,CAAC;YACb,OAAOL;QACT;QAEA,MAAMM,WAAwB;YAC5BC,SAAS;YACT,GAAIJ,OAAO;gBAACA;YAAI,IAAI,CAAC,CAAC;YACtB,GAAIJ,IAAIK,KAAK,GAAG;gBAACA,OAAOL,IAAIK,KAAK;YAAA,IAAI,CAAC,CAAC;QACzC;QAEAH,KAAKI,OAAO,CAAC,CAAC,kBAAkB,CAAC;QAEjC,OAAOC;IACT,EAAE,OAAOhB,KAAK;QACZ,MAAMC,UAAUV,gBAAgBS;QAChCW,KAAKO,IAAI,CAACjB;QACV,MAAMD;IACR;AACF"}
|
|
@@ -100,7 +100,8 @@ const MAX_CUSTOM_PROPERTY_DEPTH = 5;
|
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
102
|
* Retains serializable properties from an unknown value, recursively processing objects and arrays
|
|
103
|
-
|
|
103
|
+
* @internal Exported for testing purposes only
|
|
104
|
+
*/ export function retainSerializableProps(maybeSerializable, depth = 0) {
|
|
104
105
|
if (depth > MAX_CUSTOM_PROPERTY_DEPTH) {
|
|
105
106
|
return undefined;
|
|
106
107
|
}
|