sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
346 lines (345 loc) • 10.5 kB
JavaScript
;
var fs = require("node:fs/promises"), path = require("node:path"), node_stream = require("node:stream"), FormData = require("form-data"), nanoid = require("nanoid"), readPkgUp = require("read-pkg-up"), _internal = require("./_internal.js");
function _interopDefaultCompat(e) {
return e && typeof e == "object" && "default" in e ? e : { default: e };
}
var fs__default = /* @__PURE__ */ _interopDefaultCompat(fs), path__default = /* @__PURE__ */ _interopDefaultCompat(path), FormData__default = /* @__PURE__ */ _interopDefaultCompat(FormData), readPkgUp__default = /* @__PURE__ */ _interopDefaultCompat(readPkgUp);
const debug = _internal.debug.extend("deploy");
function promiseWithResolvers() {
let resolve, reject;
return {
promise: new Promise((res, rej) => {
resolve = res, reject = rej;
}),
resolve,
reject
};
}
async function getUserApplication({
client,
appHost,
appId
}) {
let query, uri = "/user-applications";
appId ? (uri = `/user-applications/${appId}`, query = {
appType: "coreApp"
}) : appHost ? query = {
appHost
} : query = {
default: "true"
};
try {
return await client.request({
uri,
query
});
} catch (e) {
if (e?.statusCode === 404)
return null;
throw debug("Error getting user application", e), e;
}
}
async function getUserApplications({
client,
organizationId
}) {
const query = organizationId ? {
organizationId,
appType: "coreApp"
} : {
appType: "studio"
};
try {
return await client.request({
uri: "/user-applications",
query
});
} catch (e) {
if (e?.statusCode === 404)
return null;
throw debug("Error getting user applications", e), e;
}
}
function createUserApplication(client, body, organizationId) {
const query = organizationId ? {
organizationId,
appType: "coreApp"
} : {
appType: "studio"
};
return client.request({
uri: "/user-applications",
method: "POST",
body,
query
});
}
async function selectExistingApplication({
client,
prompt,
message,
createNewLabel,
organizationId
}) {
const userApplications = await getUserApplications({
client,
organizationId
});
if (!userApplications?.length)
return null;
const choices = userApplications.map((app) => ({
value: app.appHost,
name: app.title ?? app.appHost
})), selected = await prompt.single({
message,
type: "list",
choices: [{
value: "new",
name: createNewLabel
}, new prompt.Separator(), ...choices]
});
return selected === "new" ? null : userApplications.find((app) => app.appHost === selected);
}
async function getOrCreateStudio({
client,
spinner,
context
}) {
const {
output,
prompt
} = context, existingUserApplication = await getUserApplication({
client
});
if (spinner.succeed(), existingUserApplication)
return existingUserApplication;
const selectedApp = await selectExistingApplication({
client,
prompt,
message: "Select existing studio hostname",
createNewLabel: "Create new studio hostname"
});
if (selectedApp)
return selectedApp;
output.print("Your project has not been assigned a studio hostname."), output.print("To deploy your Sanity Studio to our hosted sanity.studio service,"), output.print("you will need one. Please enter the part you want to use.");
const {
promise,
resolve
} = promiseWithResolvers();
return await prompt.single({
type: "input",
filter: (inp) => inp.replace(/\.sanity\.studio$/i, ""),
message: "Studio hostname (<value>.sanity.studio):",
// if a string is returned here, it is relayed to the user and prompt allows
// the user to try again until this function returns true
validate: async (appHost) => {
try {
const response = await createUserApplication(client, {
appHost,
urlType: "internal",
type: "studio"
});
return resolve(response), !0;
} catch (e) {
if ([402, 409].includes(e?.statusCode))
return e?.response?.body?.message || "Bad request";
throw debug("Error creating user application", e), e;
}
}
}), await promise;
}
async function getOrCreateApplication({
client,
context,
spinner
}) {
const {
prompt,
cliConfig
} = context, organizationId = cliConfig && "app" in cliConfig && cliConfig.app?.organizationId;
spinner.succeed();
const selectedApp = await selectExistingApplication({
client,
prompt,
message: "Select an existing deployed application",
createNewLabel: "Create new deployed application",
organizationId: organizationId || void 0
});
if (selectedApp)
return selectedApp;
const title = await prompt.single({
type: "input",
message: "Enter a title for your application:",
validate: (input) => input.length > 0 || "Title is required"
}), {
promise,
resolve,
reject
} = promiseWithResolvers(), tryCreateApp = async () => {
const appHost = (() => {
const firstChar = nanoid.customAlphabet("abcdefghijklmnopqrstuvwxyz", 1)(), rest = nanoid.customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789", 11)();
return `${firstChar}${rest}`;
})();
try {
const response2 = await createUserApplication(client, {
appHost,
urlType: "internal",
title,
type: "coreApp"
}, organizationId || void 0);
return resolve(response2), !0;
} catch (e) {
if ([402, 409].includes(e?.statusCode))
return debug("App host taken, retrying with new host"), tryCreateApp();
throw debug("Error creating core application", e), reject(e), e;
}
};
spinner.start("Creating application"), await tryCreateApp();
const response = await promise;
return spinner.succeed(), response;
}
async function getOrCreateStudioFromConfig({
client,
context,
spinner,
appHost
}) {
const {
output
} = context, existingUserApplication = await getUserApplication({
client,
appHost
});
if (spinner.succeed(), existingUserApplication)
return existingUserApplication;
output.print("Your project has not been assigned a studio hostname."), output.print(`Creating https://${appHost}.sanity.studio`), output.print(""), spinner.start("Creating studio hostname");
try {
const response = await createUserApplication(client, {
appHost,
urlType: "internal",
type: "studio"
});
return spinner.succeed(), response;
} catch (e) {
throw spinner.fail(), [402, 409].includes(e?.statusCode) ? new Error(e?.response?.body?.message || "Bad request") : (debug("Error creating user application from config", e), e);
}
}
async function getOrCreateAppFromConfig({
client,
context,
spinner,
appId
}) {
const {
output
} = context;
if (appId) {
const existingUserApplication = await getUserApplication({
client,
appId
});
if (spinner.succeed(), existingUserApplication)
return existingUserApplication;
}
return output.print("The id provided in your configuration is not recognized."), output.print("Checking existing applications..."), getOrCreateApplication({
client,
context,
spinner
});
}
async function getOrCreateUserApplicationFromConfig(options) {
const {
context
} = options;
if (_internal.determineIsApp(context.cliConfig))
return getOrCreateAppFromConfig(options);
if (!options.appHost)
throw new Error("Studio host was detected, but is invalid");
return getOrCreateStudioFromConfig({
...options,
appHost: options.appHost
});
}
async function createDeployment({
client,
tarball,
applicationId,
isAutoUpdating,
version,
isApp
}) {
const formData = new FormData__default.default();
return formData.append("isAutoUpdating", isAutoUpdating.toString()), formData.append("version", version), formData.append("tarball", tarball, {
contentType: "application/gzip",
filename: "app.tar.gz"
}), client.request({
uri: `/user-applications/${applicationId}/deployments`,
method: "POST",
headers: formData.getHeaders(),
body: formData.pipe(new node_stream.PassThrough()),
query: isApp ? {
appType: "coreApp"
} : {
appType: "studio"
}
});
}
async function deleteUserApplication({
applicationId,
client,
appType
}) {
await client.request({
uri: `/user-applications/${applicationId}`,
query: {
appType
},
method: "DELETE"
});
}
async function getInstalledSanityVersion() {
const sanityPkgPath = (await readPkgUp__default.default({
cwd: __dirname
}))?.path;
if (!sanityPkgPath)
throw new Error("Unable to resolve `sanity` module root");
const pkg = JSON.parse(await fs__default.default.readFile(sanityPkgPath, "utf-8"));
if (typeof pkg?.version != "string")
throw new Error("Unable to find version of `sanity` module");
return pkg.version;
}
async function dirIsEmptyOrNonExistent(sourceDir) {
try {
if (!(await fs__default.default.stat(sourceDir)).isDirectory())
throw new Error(`Directory ${sourceDir} is not a directory`);
} catch (err) {
if (err.code === "ENOENT")
return !0;
throw err;
}
return (await fs__default.default.readdir(sourceDir)).length === 0;
}
async function checkDir(sourceDir) {
try {
if (!(await fs__default.default.stat(sourceDir)).isDirectory())
throw new Error(`Directory ${sourceDir} is not a directory`);
} catch (err) {
throw err.code === "ENOENT" ? new Error(`Directory "${sourceDir}" does not exist`) : err;
}
try {
await fs__default.default.stat(path__default.default.join(sourceDir, "index.html"));
} catch (err) {
throw err.code === "ENOENT" ? new Error([`"${sourceDir}/index.html" does not exist -`, "[SOURCE_DIR] must be a directory containing", 'a Sanity studio built using "sanity build"'].join(" ")) : err;
}
}
exports.checkDir = checkDir;
exports.createDeployment = createDeployment;
exports.debug = debug;
exports.deleteUserApplication = deleteUserApplication;
exports.dirIsEmptyOrNonExistent = dirIsEmptyOrNonExistent;
exports.getInstalledSanityVersion = getInstalledSanityVersion;
exports.getOrCreateApplication = getOrCreateApplication;
exports.getOrCreateStudio = getOrCreateStudio;
exports.getOrCreateUserApplicationFromConfig = getOrCreateUserApplicationFromConfig;
exports.getUserApplication = getUserApplication;
//# sourceMappingURL=helpers.js.map