@stackbit/cms-sanity
Version:
Stackbit Sanity CMS Interface
274 lines (273 loc) • 11.6 kB
JavaScript
;
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