UNPKG

@stackbit/cms-sanity

Version:
274 lines (273 loc) 11.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.spawnFetchSchema = exports.fetchSchema = exports.fetchSchemaV3 = exports.fetchSchemaLegacy = void 0; const childProcess = __importStar(require("child_process")); const path = __importStar(require("path")); const lodash_1 = __importDefault(require("lodash")); const fs_extra_1 = __importDefault(require("fs-extra")); const utils_1 = require("@stackbit/utils"); async function fetchSchemaLegacy({ studioPath }) { console.log('fetch sanity schema legacy'); const getSanitySchema = require('@sanity/core/lib/actions/graphql/getSanitySchema'); const internalSchema = getSanitySchema(studioPath); const models = lodash_1.default.get(internalSchema, '_source.types', []); return { models: serializeValidation(models) }; } exports.fetchSchemaLegacy = fetchSchemaLegacy; async function fetchSchemaV3({ studioPath }) { console.log('fetch sanity schema v3'); // use internal Sanity API to get the schema const internalApiFileName = './node_modules/sanity/lib/_internal/cli/threads/getGraphQLAPIs.js'; const internalApiPath = path.resolve(studioPath, internalApiFileName); if (!(await fs_extra_1.default.pathExists(internalApiPath))) { throw new Error('Could not find Sanity file: ' + internalApiPath); } const internalApiPatchedFileName = path.join(path.dirname(internalApiFileName), 'getGraphQLAPIs.patched.js'); const internalApiPatchedPath = path.resolve(studioPath, internalApiPatchedFileName); // if file doesn't exist, copy it and patch it let data; if (!(await fs_extra_1.default.pathExists(internalApiPatchedPath))) { console.log('patching sanity studio file at path: ' + internalApiPath); await fs_extra_1.default.copyFile(internalApiPath, internalApiPatchedPath); data = await fs_extra_1.default.readFile(internalApiPatchedPath, 'utf8'); // monkey patch the Sanity internal API to expose getStudioConfig // and to not throw an error or run code on import data = data.replace(/throw new Error\(".*?"\);/g, 'void(0);'); data = data.replace(/\ngetGraphQLAPIsForked\(.*?\);/g, '\nvoid(0);'); if (data.includes('getStudioConfig')) { data += `\n\nexports.getStudioConfig = getStudioConfig;`; } else if (data.includes('getStudioWorkspaces.getStudioWorkspaces')) { data += `\n\nexports.getStudioWorkspaces = getStudioWorkspaces.getStudioWorkspaces;`; } await fs_extra_1.default.writeFile(internalApiPatchedPath, data); } else { console.log('sanity studio file already patched at path: ' + internalApiPath); data = await fs_extra_1.default.readFile(internalApiPatchedPath, 'utf8'); } let methodName; if (data.includes('getStudioConfig')) { methodName = 'getStudioConfig'; } else if (data.includes('getStudioWorkspaces.getStudioWorkspaces')) { methodName = 'getStudioWorkspaces'; } else { throw new Error('Could not patch Sanity file: ' + internalApiPath); } // load the patched file and use `getStudioConfig` to get the schema const getStudioConfig = require(internalApiPatchedPath)[methodName]; const internalConfig = await getStudioConfig({ basePath: studioPath }); const internalSource = internalConfig?.[0]?.unstable_sources?.[0] ?? internalConfig?.[0]; const types = internalSource?.schema?._original?.types; return { projectId: internalSource.projectId, dataset: internalSource.dataset, title: internalSource.title, models: serializeValidation(types) }; } exports.fetchSchemaV3 = fetchSchemaV3; async function fetchSchema({ studioPath }) { console.log('sanity studio path: ' + studioPath); const candidates = ['sanity.config.js', 'sanity.config.jsx', 'sanity.config.ts', 'sanity.config.tsx']; for (const sanityConfigFileName of candidates) { const sanityConfigPath = path.resolve(studioPath, sanityConfigFileName); if (await fs_extra_1.default.pathExists(sanityConfigPath)) { return fetchSchemaV3({ studioPath }); } } return fetchSchemaLegacy({ studioPath }); } exports.fetchSchema = fetchSchema; /** * Serializes Sanity validation functions into JSON * https://www.sanity.io/docs/validation * * Supported validation functions: * Rule.required() * Rule.integer() * Rule.min(n) * Rule.max(n) * Rule.length(n) * * Rule => Rule.required() * => { isRequired: true } * * // Also supports array of rules as defined in Sanity docs: * Rule => [ * Rule.required(), * Rule.integer() * ] * => { isRequired: true, isInteger: true } * Rule => Rule.required().min(10).max(80) * => { isRequired: true, min: 10, max: 80 } * * @param models * @return {*} */ function serializeValidation(models) { const Rule = require('@sanity/validation').Rule; return (0, utils_1.deepMap)(models, (value, keyPath, objectStack) => { if (lodash_1.default.isFunction(value) && lodash_1.default.last(keyPath) === 'validation') { const field = lodash_1.default.last(objectStack); const rules = lodash_1.default.castArray(field.validation(new Rule())); const isRequired = lodash_1.default.some(rules, (rule) => rule.isRequired()); const _rules = lodash_1.default.flatten(lodash_1.default.map(rules, '_rules')); const isInteger = lodash_1.default.some(_rules, (rule) => rule.flag === 'integer') || undefined; const min = lodash_1.default.get(lodash_1.default.find(_rules, (rule) => rule.flag === 'min'), 'constraint'); const max = lodash_1.default.get(lodash_1.default.find(_rules, (rule) => rule.flag === 'max'), 'constraint'); const length = lodash_1.default.get(lodash_1.default.find(_rules, (rule) => rule.flag === 'length'), 'constraint'); return lodash_1.default.omitBy({ isRequired, isInteger, min, max, length }, lodash_1.default.isNil); } return value; }); } function spawnFetchSchema({ studioPath, nodePath, repoPath, spawnRunner, logger }) { logger?.debug(`[sanity-schema-fetcher] spawn fetch schema, studioPath: ${studioPath}, repoPath: ${repoPath} using spawnRunner: ${!!spawnRunner}`); return new Promise((resolve, reject) => { let done = false; let errOutput = ''; const buffers = { stdout: '', stderr: '' }; let schema = null; const finish = (code) => { if (done) { return; } done = true; flush(); if (code === 0) { resolve(schema); } else { reject(errOutput); } }; const writeLine = (type, line) => { if (lodash_1.default.isEmpty(lodash_1.default.trim(line))) { return; } if (type === 'stderr') { logger?.error(`[sanity-schema-fetcher] stderr: ${line}`); } else if (line.indexOf('SCHEMA_OUTPUT') !== -1) { logger?.debug(`[sanity-schema-fetcher] got schema`); schema = JSON.parse(line.match(/SCHEMA_OUTPUT:(.*)/)[1]); } else { logger?.debug(`[sanity-schema-fetcher] stdout: ${line}`); } }; const handler = (type, data) => { const lines = lodash_1.default.split(data, '\n'); if (buffers[type]) { lines[0] = buffers[type] + lines[0]; buffers[type] = ''; } if (lodash_1.default.last(lines) !== '') { buffers[type] = lines.pop() || ''; } if (type === 'stderr') { errOutput += data; } lodash_1.default.forEach(lines, (line) => { writeLine(type, line); }); }; const flush = () => { lodash_1.default.forEach(buffers, (line, key) => { writeLine(key, line); }); }; const proc = spawnRunner?.({ command: 'node', args: [__filename, studioPath], cwd: studioPath, env: { NODE_PATH: nodePath || `${path.join(studioPath, 'node_modules')}:${path.join(repoPath, 'node_modules')}` } }) ?? // To debug the subprocess add --inspect=9229 // args: ['--inspect=9229', __filename, studioPath] childProcess.spawn('node', [__filename, studioPath], { cwd: studioPath, env: lodash_1.default.assign({}, process.env, { NODE_PATH: nodePath || `${path.join(studioPath, 'node_modules')}:${path.join(repoPath, 'node_modules')}` }) }); proc.stdout.on('data', lodash_1.default.partial(handler, 'stdout')); proc.stderr.on('data', lodash_1.default.partial(handler, 'stderr')); proc.on('error', (error) => { logger?.debug(`[sanity-schema-fetcher] error: ${error}`); }); proc.on('close', (code) => { if (code != 0) { logger?.debug(`[sanity-schema-fetcher] closed with code: ${code}`); } finish(code); }); proc.on('exit', (code) => { if (code != 0) { logger?.debug(`[sanity-schema-fetcher] exited with code: ${code}`); } finish(code); }); }); } exports.spawnFetchSchema = spawnFetchSchema; if (require.main === module) { const studioPath = process.argv[2]; if (!studioPath) { console.error('[sanity-schema-fetcher] missing studio path'); process.exit(1); } console.log(`[sanity-schema-fetcher] fetch schema, studioPath: ${studioPath}`); fetchSchema({ studioPath }) .then((schema) => { console.log(`[sanity-schema-fetcher] done fetching`); process.stdout.write(`SCHEMA_OUTPUT:${JSON.stringify(schema)}`, () => { process.exit(0); }); }) .catch((err) => { console.error(`[sanity-schema-fetcher] error fetching, error: ${err.message}`, err.stack); process.exit(1); }); } //# sourceMappingURL=sanity-schema-fetcher.js.map