studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
356 lines (355 loc) • 14 kB
JavaScript
import { promises as fsP, writeFileSync } from "node:fs";
import { runtimeLogger } from "@inox-tools/runtime-logger";
import studiocmsUi from "@studiocms/ui";
import { componentRegistryHandler } from "@withstudiocms/component-registry";
import { configResolverBuilder, exists, watchConfigFileBuilder } from "@withstudiocms/config-utils";
import {
addIntegrationArray,
getLatestVersion,
injectScripts,
integrationLogger,
logMessages
} from "@withstudiocms/internal_helpers/astro-integration";
import { readJson } from "@withstudiocms/internal_helpers/utils";
import { envField } from "astro/config";
import { z } from "astro/zod";
import { addVirtualImports, createResolver, defineIntegration } from "astro-integration-kit";
import { compare as semCompare } from "semver";
import { loadEnv } from "vite";
import {
AstroConfigImageSettings,
AstroConfigViteSettings,
getUiOpts,
makeDashboardRoute,
routesDir
} from "./consts.js";
import {
changelogHelper,
checkAstroConfig,
pluginHandler,
routeHandler
} from "./handlers/index.js";
import { nodeNamespaceBuiltinsAstro } from "./integrations/node-namespace.js";
import {
StudioCMSOptionsSchema
} from "./schemas/index.js";
import { availableTranslationFileKeys, availableTranslations } from "./virtuals/i18n/v-files.js";
import { VirtualModuleBuilder } from "./virtuals/utils.js";
const { resolve } = createResolver(import.meta.url);
const { name: pkgName, version: pkgVersion } = readJson(
resolve("../package.json")
);
const env = loadEnv("", process.cwd(), "");
const StudioCMSRendererComponentPath = "./virtuals/components/Renderer.astro";
const CustomImageComponentPath = "./virtuals/components/CustomImage.astro";
const builtInComponents = {
"cms-img": resolve(CustomImageComponentPath)
};
const configPaths = [
"studiocms.config.js",
"studiocms.config.mjs",
"studiocms.config.cjs",
"studiocms.config.ts",
"studiocms.config.mts",
"studiocms.config.cts"
];
const studiocms = defineIntegration({
name: pkgName,
optionsSchema: z.custom(),
setup: ({ name, options: opts }) => {
let options;
const watchConfigFile = watchConfigFileBuilder({
configPaths
});
const configResolver = configResolverBuilder({
configPaths,
label: name,
zodSchema: StudioCMSOptionsSchema
});
const messages = [];
let cacheJsonFile;
let isDevMode = false;
return {
name,
hooks: {
// Expose plugins defined in Astro config
"studiocms:plugins": ({ exposePlugins }) => {
exposePlugins(opts?.plugins);
},
// DB Setup: Setup the Database Connection for AstroDB and StudioCMS
"astro:db:setup": ({ extendDb }) => {
extendDb({ configEntrypoint: resolve("./db/config.js") });
},
"astro:config:setup": async (params) => {
const { logger, updateConfig, createCodegenDir, command, addMiddleware } = params;
logger.info("Checking configuration...");
isDevMode = command === "dev";
watchConfigFile(params);
options = await configResolver(params, opts);
const {
dbStartPage,
plugins,
verbose,
componentRegistry,
features: {
developerConfig,
robotsTXT: robotsTXTConfig,
injectQuickActionsMenu,
dashboardConfig: { dashboardEnabled, inject404Route, dashboardRouteOverride },
authConfig
}
} = options;
const shouldInject404Route = inject404Route && dashboardEnabled;
const logInfo = { logger, logLevel: "info", verbose };
const dashboardRoute = makeDashboardRoute(dashboardRouteOverride);
integrationLogger(logInfo, "Setting up StudioCMS...");
runtimeLogger(params, {
name: "studiocms-runtime"
});
checkAstroConfig(params);
integrationLogger(logInfo, "Setting up StudioCMS internals...");
changelogHelper(params, resolve("../CHANGELOG.md"));
injectScripts(params, [
{
stage: "page",
content: await fsP.readFile(
resolve("./virtuals/scripts/user-quick-tools.js"),
"utf-8"
),
enabled: injectQuickActionsMenu && !dbStartPage
}
]);
await componentRegistryHandler(params, {
config: {
name,
verbose,
virtualId: "studiocms:component-registry"
},
componentRegistry,
builtInComponents
});
const {
extraRoutes,
integrations: pluginIntegrations,
safePluginList,
messages: pluginMessages,
oAuthProvidersConfigured
} = await pluginHandler(params, {
dashboardRoute,
dbStartPage,
name,
pkgVersion,
plugins,
robotsTXTConfig,
verbose
});
routeHandler(params, {
oAuthProvidersConfigured,
authConfig,
dashboardEnabled,
dashboardRoute,
dbStartPage,
developerConfig,
extraRoutes,
shouldInject404Route
});
if (!dbStartPage)
addMiddleware({
order: "pre",
entrypoint: routesDir.middleware("index.ts")
});
addIntegrationArray(params, [
{ integration: nodeNamespaceBuiltinsAstro() },
{ integration: studiocmsUi(getUiOpts()) },
...pluginIntegrations
]);
integrationLogger(logInfo, "Adding Virtual Imports...");
const {
dynamicVirtual,
ambientScripts,
namedVirtual,
astroComponentVirtual,
dynamicWithAstroVirtual,
buildDefaultOnlyVirtual,
buildLoggerVirtual,
buildNamedMultiExportVirtual,
buildVirtualConfig
} = VirtualModuleBuilder(resolve);
addVirtualImports(params, {
name,
imports: {
"studiocms:config": buildVirtualConfig(options),
"studiocms:plugins": buildDefaultOnlyVirtual(safePluginList),
"studiocms:version": buildDefaultOnlyVirtual(pkgVersion),
"studiocms:logger": buildLoggerVirtual(verbose),
"virtual:studiocms/sdk/env": buildNamedMultiExportVirtual({
dbUrl: env.ASTRO_DB_REMOTE_URL,
dbSecret: env.ASTRO_DB_APP_TOKEN,
cmsEncryptionKey: env.CMS_ENCRYPTION_KEY
}),
"studiocms:lib": dynamicVirtual([
"./virtuals/lib/head.js",
"./virtuals/lib/headDefaults.js",
"./virtuals/lib/pathGenerators.js",
"./virtuals/lib/routeMap.js",
"./virtuals/lib/urlGen.js"
]),
"studiocms:notifier": dynamicVirtual(["./virtuals/notifier/index.js"]),
"studiocms:notifier/client": dynamicVirtual(["./virtuals/notifier/client.js"]),
"studiocms:mailer": dynamicVirtual(["./virtuals/mailer/index.js"]),
"studiocms:mailer/templates": namedVirtual({
namedExport: "getTemplate",
path: "./virtuals/mailer/template.js",
exportDefault: true
}),
"studiocms:components": astroComponentVirtual({
FormattedDate: "./virtuals/components/FormattedDate.astro",
Generator: "./virtuals/components/Generator.astro"
}),
"studiocms:renderer": astroComponentVirtual({
StudioCMSRenderer: StudioCMSRendererComponentPath
}),
"studiocms:imageHandler/components": astroComponentVirtual({
CustomImage: CustomImageComponentPath
}),
"studiocms:plugin-helpers": dynamicVirtual([
"./plugins.js",
"./virtuals/plugins/index.js"
]),
"studiocms:auth/lib": dynamicVirtual(["./virtuals/auth/index.js"]),
"studiocms:auth/lib/types": dynamicVirtual(["./virtuals/auth/types.js"]),
"studiocms:auth/utils/validImages": dynamicVirtual([
"./virtuals/auth/validImages/index.js"
]),
"studiocms:auth/utils/getLabelForPermissionLevel": dynamicVirtual([
"./virtuals/auth/getLabelForPermissionLevel.js"
]),
"studiocms:auth/scripts/three": ambientScripts(["./virtuals/auth/scripts/three.js"]),
"studiocms:i18n/virtual": `
export const availableTranslationFileKeys = ${JSON.stringify(availableTranslationFileKeys)};
export const availableTranslations = ${JSON.stringify(availableTranslations)};
`,
"studiocms:i18n": dynamicWithAstroVirtual({
dynamicExports: ["./virtuals/i18n/server.js"],
astroComponents: {
LanguageSelector: "./virtuals/i18n/LanguageSelector.astro"
}
}),
"studiocms:i18n/client": dynamicVirtual(["./virtuals/i18n/client.js"]),
"studiocms:sdk": dynamicVirtual(["./virtuals/sdk/index.js"]),
"studiocms:sdk/types": dynamicVirtual(["./virtuals/sdk/types.js"])
}
});
integrationLogger(
logInfo,
"Updating Astro Config with StudioCMS Resources and settings..."
);
updateConfig({
image: AstroConfigImageSettings,
vite: AstroConfigViteSettings,
env: {
validateSecrets: true,
schema: {
// Auth Encryption Key
CMS_ENCRYPTION_KEY: envField.string({
context: "server",
access: "secret",
optional: false
})
}
}
});
if (pluginMessages.length > 0) {
messages.push(...pluginMessages);
}
const codegenDir = createCodegenDir();
cacheJsonFile = new URL("cache.json", codegenDir);
if (!exists(cacheJsonFile.href)) {
writeFileSync(cacheJsonFile, "{}", "utf-8");
}
},
"astro:config:done": ({ config }) => {
messages.push({
label: "studiocms:setup",
logLevel: "info",
message: "Setup Complete. \u{1F680}"
});
if (options.dbStartPage) {
if (isDevMode) {
messages.push({
label: "studiocms:start-page",
logLevel: "warn",
message: `Start Page is enabled. This will be the only page available until you initialize your database and disable the config option that forces this page to be displayed. To get started, visit http://localhost:${config.server.port}/start/ in your browser to initialize your database and set up your installation.`
});
} else {
messages.push({
label: "studiocms:start-page",
logLevel: "error",
message: "Start Page is enabled. Please ensure you have set up your StudioCMS database and disabled the Start Page before building for production."
});
}
}
if (options.features.developerConfig.demoMode !== false) {
messages.push({
label: "studiocms:demo-mode",
logLevel: "info",
message: "Demo Mode is Enabled. This means that the StudioCMS Dashboard will be available to the public using the provided credentials and the REST API has been disabled. To disable Demo Mode, set the `demoMode` option to `false` or remove the option in your StudioCMS configuration."
});
}
},
// DEV SERVER: Check for updates on server start and log messages
"astro:server:start": async ({ logger }) => {
function logUpdateCheck(logLevel, message) {
messages.push({
label: "studiocms:update-check",
logLevel,
message
});
}
try {
const latestVersion = await getLatestVersion(
pkgName,
logger.fork("studiocms:update-check"),
cacheJsonFile,
isDevMode
);
if (latestVersion) {
const comparison = semCompare(pkgVersion, latestVersion);
if (comparison === -1) {
logUpdateCheck(
"warn",
`A new version of '${name}' is available. Please update to ${latestVersion} using your favorite package manager.`
);
} else if (comparison === 0) {
logUpdateCheck(
"info",
`You are using the latest version of '${name}' (${pkgVersion})`
);
} else {
logUpdateCheck(
"info",
`You are using a newer version (${pkgVersion}) of '${name}' than the latest release (${latestVersion})`
);
}
}
} catch (error) {
logUpdateCheck(
"error",
`Error fetching latest version from npm registry: ${error instanceof Error ? error.message : String(error)}`
);
}
await logMessages(messages, options, logger);
},
// BUILD: Log messages at the end of the build
"astro:build:done": async ({ logger }) => {
await logMessages(messages, options, logger);
}
}
};
}
});
var index_default = studiocms;
export {
index_default as default,
studiocms
};