@knapsack/app
Version:
Build Design Systems on top of knapsack, by Basalt
396 lines (338 loc) • 11.4 kB
JavaScript
;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getServer = getServer;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _express = _interopRequireDefault(require("express"));
var _apolloServerExpress = require("apollo-server-express");
var _graphqlTools = require("graphql-tools");
var _ws = _interopRequireDefault(require("ws"));
var _bodyParser = _interopRequireDefault(require("body-parser"));
require("isomorphic-fetch");
var _path = require("path");
var _fsExtra = require("fs-extra");
var _cloudConnect = require("../cloud/cloud-connect");
var log = _interopRequireWildcard(require("../cli/log"));
var _events = require("./events");
var _restApi = require("./rest-api");
var _routes = require("./routes");
var _features = require("../lib/features");
var _auth = require("./auth");
var _constants = require("../lib/constants");
var _pageBuilder = require("./page-builder");
var _designTokens = require("./design-tokens");
var _bootstrap = require("../lib/bootstrap");
var _webSockets = require("../schemas/web-sockets");
var _utils = require("../lib/utils");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
async function getServer({
meta
}) {
const {
patterns,
customPages,
pageBuilderPages,
settings,
tokens,
navs,
config,
assetSets
} = (0, _bootstrap.getBrain)();
const port = process.env.KNAPSACK_PORT || meta.serverPort;
const knapsackDistDir = (0, _path.join)(__dirname, '../../dist/client');
const metaTypeDef = (0, _apolloServerExpress.gql)`
type Meta {
websocketsPort: Int!
knapsackVersion: String
changelog: String
version: String
}
type Query {
meta: Meta
}
`;
const metaResolvers = {
Query: {
meta: () => meta
}
};
const gqlServer = new _apolloServerExpress.ApolloServer({
schema: (0, _graphqlTools.mergeSchemas)({
schemas: [(0, _graphqlTools.makeExecutableSchema)({
typeDefs: _pageBuilder.pageBuilderPagesTypeDef,
resolvers: _pageBuilder.pageBuilderPagesResolvers
}), (0, _graphqlTools.makeExecutableSchema)({
typeDefs: _designTokens.designTokensTypeDef,
resolvers: _designTokens.designTokensResolvers
}), // makeExecutableSchema({
// typeDefs: patternsTypeDef,
// resolvers: patternsResolvers,
// }),
(0, _graphqlTools.makeExecutableSchema)({
typeDefs: metaTypeDef,
resolvers: metaResolvers
})]
}),
// https://www.apollographql.com/docs/apollo-server/essentials/data.html#context
context: ({
req
}) => {
// const { host, origin } = req.headers;
// log.verbose('request received', { host, origin }, 'graphql');
const {
role
} = (0, _auth.getUserInfo)(req);
const canWrite = role.permissions.includes(_constants.PERMISSIONS.WRITE);
return {
pageBuilderPages,
settings,
tokens,
assetSets,
navs,
patterns,
canWrite,
customPages,
config
};
},
playground: true,
introspection: true
});
const app = (0, _express.default)();
app.use(_bodyParser.default.json({
limit: '5000kb'
}));
gqlServer.applyMiddleware({
app
});
const endpoints = [];
function registerEndpoint(pathname, method = 'GET') {
endpoints.push({
pathname,
method
});
}
const restApiRoutes = (0, _restApi.getApiRoutes)({
registerEndpoint,
webroot: config.dist,
public: config.public,
baseUrl: _constants.apiUrlBase,
meta,
patternManifest: patterns,
templateRenderers: config.templateRenderers,
pageBuilder: pageBuilderPages,
settingsStore: settings,
tokens,
config
});
app.use(restApiRoutes);
async function getDataStore() {
return {
settingsState: {
settings: await settings.getData()
},
patternsState: await patterns.getData(),
customPagesState: await customPages.getData(),
assetSetsState: await assetSets.getData(),
navsState: await navs.getData()
};
}
const fileSavers = {};
async function saveFilesLocally({
files
}) {
await Promise.all(files.map(({
contents,
path,
encoding,
isDeleted
}) => {
if (isDeleted) {
return (0, _fsExtra.remove)(path);
}
switch (encoding) {
case 'utf8':
return (0, _fsExtra.writeFile)(path, contents, {
encoding
});
case 'base64':
{
const data = Buffer.from(contents, 'base64');
return (0, _fsExtra.writeFile)(path, data);
}
default:
throw new Error(`Incorrect encoding used when saving files locally: "${encoding}" for file ${path}.`);
}
}));
return {
ok: true,
message: 'All config files saved locally.'
};
}
fileSavers.local = saveFilesLocally;
if (config.cloud) {
const {
saveFilesToCloud
} = new _cloudConnect.KsCloudConnect(config.cloud);
fileSavers.cloud = saveFilesToCloud;
}
async function handleNewDataStore({
state,
title,
message,
storageLocation,
user
}) {
const configFiles = await Promise.all([settings.savePrep(state.settingsState.settings), customPages.savePrep(state.customPagesState), navs.savePrep(state.navsState), patterns.savePrep(state.patternsState)]).then(results => (0, _utils.flattenArray)(results));
configFiles.forEach(configFile => {
if (configFile.encoding === 'base64') {
if (!(0, _utils.isBase64)(configFile.contents)) {
console.log(configFile);
throw new Error(`Pre-save check on Knapsack File "${configFile.path}" expected a base64 encoding and it is not.`);
}
}
});
if (!fileSavers[storageLocation]) {
throw new Error(`Must declare save location, passed in ${storageLocation}`);
}
return fileSavers[storageLocation]({
files: configFiles,
title,
message,
user
});
}
app.get(`${_constants.apiUrlBase}/data-store`, async (req, res) => {
const dataStore = await getDataStore();
const userInfo = (0, _auth.getUserInfo)(req);
const features = (0, _features.getFeaturesForUser)(userInfo);
log.verbose('features', features);
const fullDataStore = _objectSpread({}, dataStore, {
userState: {
isLocalDev: process.env.NODE_ENV !== 'production',
canEdit: process.env.NODE_ENV !== 'production',
features
},
metaState: {
meta,
plugins: config.plugins.map(p => {
return {
id: p.id,
hasContent: !!p.loadContent,
clientPluginPath: p.clientPluginPath ? (0, _path.join)(`/plugins/${p.id}`, p.clientPluginPath) : null
};
})
}
});
res.send(fullDataStore);
});
app.post(`${_constants.apiUrlBase}/data-store`, async (req, res) => {
var _user$role;
const {
state,
title,
message,
storageLocation
} = req.body;
if (!(storageLocation === 'local' || storageLocation === 'cloud')) {
return res.status(_constants.HTTP_STATUS.BAD.BAD_REQUEST).send({
ok: false,
message: `loc param must be local or cloud, was: ${storageLocation}`
});
}
const isLocalDev = state.userState.isLocalDev && process.env.NODE_ENV === 'production';
const user = (0, _auth.getUserInfo)(req);
const permissions = user === null || user === void 0 ? void 0 : (_user$role = user.role) === null || _user$role === void 0 ? void 0 : _user$role.permissions;
if (!permissions.includes(_constants.PERMISSIONS.WRITE) && !isLocalDev && storageLocation !== 'local') {
res.status(_constants.HTTP_STATUS.BAD.UNAUTHORIZED).send();
} else {
try {
const results = await handleNewDataStore({
storageLocation,
title,
message,
state,
user
}); // console.log('handleNewDataStore results', results);
res.send(results);
} catch (e) {
console.error('handleNewDataStore', e);
res.status(_constants.HTTP_STATUS.FAIL.INTERNAL_ERROR).send({
ok: false,
message: `Could not handleNewDataStore. ${e.message}`
});
}
}
});
const regularRoutes = (0, _routes.setupRoutes)({
patterns,
knapsackDistDir,
distDir: config.dist,
publicDir: config.public,
cacheDir: meta.cacheDir,
plugins: config.plugins
});
app.use(regularRoutes);
let wss;
/**
* @returns if successful
*/
function sendWsMessage(msg) {
if (!wss) {
console.error('Attempted to fire "sendWsMessage" but no WebSockets Server setup due to lack of "websocketsPort" in config');
return false;
}
log.verbose('sendWsMessage', msg, 'server');
wss.clients.forEach(client => {
if (client.readyState === _ws.default.OPEN) {
client.send(JSON.stringify(msg));
}
});
return true;
}
function serve() {
if (meta.websocketsPort && _features.enableTemplatePush) {
wss = new _ws.default.Server({
port: meta.websocketsPort,
clientTracking: true
}); // wss.on('connection', ws => {
// ws.on('message', msg => {
// console.log('wss messge', msg);
// });
// });
}
app.listen(port, () => {
log.silly('Available endpoints', endpoints, 'server'); // want url to not get buried with info
// @todo show this after event is fired from WebPack being ready
setTimeout(() => {
log.info(`🚀 Server listening on http://localhost:${port}`, null, 'server');
}, 250);
});
if (_features.enableTemplatePush && wss) {
_events.knapsackEvents.on(_events.EVENTS.PATTERN_TEMPLATE_CHANGED, data => {
setTimeout(() => {
sendWsMessage({
event: _webSockets.WS_EVENTS.PATTERN_TEMPLATE_CHANGED,
data
});
}, 100);
});
_events.knapsackEvents.on(_events.EVENTS.PATTERN_ASSET_CHANGED, data => {
setTimeout(() => {
sendWsMessage({
event: _webSockets.WS_EVENTS.PATTERN_ASSET_CHANGED,
data
});
}, 100);
});
}
}
return {
app,
serve
};
}