@stackbit/dev 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/loading-page.html +54 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -0
- package/dist/consts.d.ts +12 -0
- package/dist/consts.js +47 -0
- package/dist/consts.js.map +1 -0
- package/dist/dev.d.ts +6 -0
- package/dist/dev.js +57 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/runner/index.d.ts +38 -0
- package/dist/runner/index.js +141 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/ssg.d.ts +9 -0
- package/dist/runner/ssg.js +15 -0
- package/dist/runner/ssg.js.map +1 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +51 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/proxy.d.ts +2 -0
- package/dist/server/proxy.js +81 -0
- package/dist/server/proxy.js.map +1 -0
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.js +190 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/services/api.d.ts +4 -0
- package/dist/services/api.js +15 -0
- package/dist/services/api.js.map +1 -0
- package/dist/services/file-watcher.d.ts +2 -0
- package/dist/services/file-watcher.js +42 -0
- package/dist/services/file-watcher.js.map +1 -0
- package/dist/services/logger.d.ts +6 -0
- package/dist/services/logger.js +35 -0
- package/dist/services/logger.js.map +1 -0
- package/dist/services/ngrok.d.ts +1 -0
- package/dist/services/ngrok.js +18 -0
- package/dist/services/ngrok.js.map +1 -0
- package/dist/services/user-config.d.ts +1 -0
- package/dist/services/user-config.js +16 -0
- package/dist/services/user-config.js.map +1 -0
- package/package.json +59 -0
- package/src/assets/loading-page.html +54 -0
- package/src/config.ts +6 -0
- package/src/consts.ts +44 -0
- package/src/dev.ts +65 -0
- package/src/index.ts +48 -0
- package/src/runner/index.ts +162 -0
- package/src/runner/ssg.ts +10 -0
- package/src/server/index.ts +65 -0
- package/src/server/proxy.ts +85 -0
- package/src/server/routes.ts +186 -0
- package/src/services/api.ts +10 -0
- package/src/services/file-watcher.ts +37 -0
- package/src/services/logger.ts +34 -0
- package/src/services/ngrok.ts +11 -0
- package/src/services/user-config.ts +14 -0
- package/src/types/http-proxy-middleware.d.ts +3 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
|
|
5
|
+
yargs(process.argv.slice(2))
|
|
6
|
+
.command(
|
|
7
|
+
'$0',
|
|
8
|
+
'stackbit-dev',
|
|
9
|
+
(yargs) =>
|
|
10
|
+
yargs
|
|
11
|
+
.option('port', {
|
|
12
|
+
alias: 'p',
|
|
13
|
+
description: 'port',
|
|
14
|
+
default: 3000
|
|
15
|
+
})
|
|
16
|
+
.options('host', {
|
|
17
|
+
alias: 'h',
|
|
18
|
+
description: 'hostname',
|
|
19
|
+
default: 'localhost'
|
|
20
|
+
})
|
|
21
|
+
.options('dir', {
|
|
22
|
+
alias: 'd',
|
|
23
|
+
description: 'root dir',
|
|
24
|
+
default: process.cwd()
|
|
25
|
+
})
|
|
26
|
+
.options('runnable-dir', {
|
|
27
|
+
description: 'runnable dir',
|
|
28
|
+
default: ''
|
|
29
|
+
})
|
|
30
|
+
.options('log-level', {
|
|
31
|
+
description: 'log level',
|
|
32
|
+
default: 'info'
|
|
33
|
+
})
|
|
34
|
+
.options('help', {}),
|
|
35
|
+
async (argv) => {
|
|
36
|
+
if (argv['log-level']) {
|
|
37
|
+
process.env.LOG_LEVEL = argv['log-level'];
|
|
38
|
+
}
|
|
39
|
+
return require('./dev').start({
|
|
40
|
+
ssgPort: argv.port,
|
|
41
|
+
ssgHost: argv.host,
|
|
42
|
+
rootDir: argv.dir,
|
|
43
|
+
runnableDir: argv['runnable-dir']
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
.demandCommand()
|
|
48
|
+
.parse();
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ssg from './ssg';
|
|
4
|
+
import Editor from '@stackbit/dev-common/dist/runner/editor';
|
|
5
|
+
import GitCMS from '@stackbit/dev-common/dist/cms/git';
|
|
6
|
+
import Worker from '@stackbit/dev-common/dist/utils/worker';
|
|
7
|
+
import { StackbitYaml } from '@stackbit/dev-common/dist/services/stackbit-yaml';
|
|
8
|
+
import { watchDir } from '../services/file-watcher';
|
|
9
|
+
|
|
10
|
+
import logger from '../services/logger';
|
|
11
|
+
import consts from '../consts';
|
|
12
|
+
|
|
13
|
+
export default class Runner {
|
|
14
|
+
private editor: any;
|
|
15
|
+
private cms: any;
|
|
16
|
+
private stackbitYaml: any;
|
|
17
|
+
private workers: any;
|
|
18
|
+
private watcher: any;
|
|
19
|
+
|
|
20
|
+
private rootDir: string;
|
|
21
|
+
private appDir?: string;
|
|
22
|
+
|
|
23
|
+
constructor({ rootDir, runnableDir }: { rootDir: string; runnableDir?: string }) {
|
|
24
|
+
this.rootDir = rootDir;
|
|
25
|
+
this.appDir = runnableDir ? path.join(this.rootDir, runnableDir) : this.rootDir;
|
|
26
|
+
this.workers = {
|
|
27
|
+
gitApp: new Worker()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async install() {
|
|
32
|
+
this.stackbitYaml = new StackbitYaml({
|
|
33
|
+
repoDir: this.rootDir,
|
|
34
|
+
appDir: this.appDir,
|
|
35
|
+
extendLogicFields: null,
|
|
36
|
+
onNodeVersionUpdated: null,
|
|
37
|
+
logger,
|
|
38
|
+
userLogger: logger,
|
|
39
|
+
envName: 'local' //TODO
|
|
40
|
+
});
|
|
41
|
+
await this.stackbitYaml.load();
|
|
42
|
+
this.cms = new GitCMS({
|
|
43
|
+
rootDir: this.rootDir,
|
|
44
|
+
repoDir: this.rootDir,
|
|
45
|
+
appDir: this.appDir,
|
|
46
|
+
stackbitYaml: this.stackbitYaml,
|
|
47
|
+
workers: this.workers,
|
|
48
|
+
assetIdPrefix: consts.ASSET_ID_PREFIX,
|
|
49
|
+
staticAssetsFilePath: consts.STATIC_ASSETS_FILE_PATH,
|
|
50
|
+
staticAssetsPublicPath: consts.STATIC_ASSETS_PUBLIC_PATH,
|
|
51
|
+
staticThemeAssetsFilePath: consts.STATIC_THEME_ASSETS_FILE_PATH,
|
|
52
|
+
staticThemeAssetsPublicPath: consts.STATIC_THEME_ASSETS_PUBLIC_PATH,
|
|
53
|
+
defaultContentTypeExtensions: consts.DEFAULT_CONTENT_TYPE_EXTENSIONS,
|
|
54
|
+
logger,
|
|
55
|
+
userLogger: logger
|
|
56
|
+
});
|
|
57
|
+
await this.cms.run();
|
|
58
|
+
this.editor = new Editor(null, this.cms, this.stackbitYaml, {
|
|
59
|
+
logger,
|
|
60
|
+
stackbitUrlPathField: consts.STACKBIT_URL_PATH_FIELD
|
|
61
|
+
});
|
|
62
|
+
this.watcher = watchDir(this.rootDir, (filePaths) => {
|
|
63
|
+
logger.debug(`File change detected: ${filePaths}`);
|
|
64
|
+
this.handleFileChanges(filePaths);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
handleFileChanges(filePaths: string[]) {
|
|
69
|
+
this.cms.postPull(filePaths.map((filePath) => path.join(this.rootDir, filePath))).catch((err: any) => {
|
|
70
|
+
logger.debug('Error in postPull', { err });
|
|
71
|
+
});
|
|
72
|
+
for (const filePath of filePaths) {
|
|
73
|
+
if (!StackbitYaml.isStackbitYamlFile(filePath)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
this.stackbitYaml.load().catch((err: any) => {
|
|
77
|
+
logger.debug('Error handling stackbit.yaml change', { err });
|
|
78
|
+
logger.error('Error reloading stackbit.yaml');
|
|
79
|
+
});
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getDirectPaths() {
|
|
85
|
+
return ssg.directPaths;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Function retuning a map or a function that rewrites direct proxy requests.
|
|
90
|
+
* https://github.com/chimurai/http-proxy-middleware#http-proxy-middleware-options
|
|
91
|
+
* @return {Function|Object}
|
|
92
|
+
*/
|
|
93
|
+
getDirectRoutes(): { [hostOrPath: string]: string } {
|
|
94
|
+
return ssg.directRoutes;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getDirectChangeOrigin() {
|
|
98
|
+
return ssg.directChangeOrigin;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getDirectChangeSocketOrigin() {
|
|
102
|
+
return ssg.directChangeSocketOrigin;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async getObjects(objectIds = []) {
|
|
106
|
+
return this.editor.getObjects(objectIds);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async getObjectsWithAnnotations(annotationTree = null, clientAnnotationErrors = []) {
|
|
110
|
+
const result = this.editor.getObjectsWithAnnotations(annotationTree);
|
|
111
|
+
_.forEach([...(result.errors ?? []), ...clientAnnotationErrors], (error) => {
|
|
112
|
+
const errorProps = _.omit(error, ['type', 'message']);
|
|
113
|
+
logger.error(`${error.message} ${_.map(errorProps, (val, key) => `${key}:'${val && val.toString ? val.toString() : val}'`).join(', ')}`);
|
|
114
|
+
});
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getCollections() {
|
|
119
|
+
return this.editor.getCollections();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getSiteMap() {
|
|
123
|
+
return this.editor.getSiteMap(this.cms.getEncodingResult());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getSchema() {
|
|
127
|
+
return this.editor.getSchema();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getUrl(objectId: string, projectId: string, locale: string): Promise<any> {
|
|
131
|
+
return this.editor.getUrl(objectId, projectId, locale);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async getObject(objectId: string, projectId: string): Promise<any> {
|
|
135
|
+
return this.cms.getObject(objectId, projectId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
createObject(modelName: string, filePath: any, object: string, user: any): Promise<any> {
|
|
139
|
+
return this.cms.createObject(modelName, filePath, object, user);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
deleteObject(objectId: string, user: any): Promise<any> {
|
|
143
|
+
return this.cms.deleteObject(objectId, user);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
updateObject(objectId: string, projectId: string, object: any, user: any): Promise<any> {
|
|
147
|
+
return this.getObject(objectId, projectId).then((existingObject) => {
|
|
148
|
+
if (!existingObject) {
|
|
149
|
+
throw new Error('Object not found');
|
|
150
|
+
}
|
|
151
|
+
return this.cms.updateObject(existingObject, object, user);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async listAssets(filterParams: any): Promise<any> {
|
|
156
|
+
return this.cms.listAssets(filterParams);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async uploadAsset(url: string, filename: string, user: any): Promise<any> {
|
|
160
|
+
return this.cms.uploadAsset(url, filename, user);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import consts from '../consts';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
directPaths: ['/_next/**', '/nextjs-live-updates/**'],
|
|
5
|
+
directRoutes: {
|
|
6
|
+
'/socket.io': `http://localhost:${consts.NEXTJS_SOURCEBIT_LIVE_UPDATE_PORT}`
|
|
7
|
+
},
|
|
8
|
+
directChangeSocketOrigin: false,
|
|
9
|
+
directChangeOrigin: true
|
|
10
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import _, { reject } from 'lodash';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import logger from '../services/logger';
|
|
7
|
+
|
|
8
|
+
import routes from './routes';
|
|
9
|
+
import proxy from './proxy';
|
|
10
|
+
import Runner from '../runner';
|
|
11
|
+
import consts from '../consts';
|
|
12
|
+
import socketService from '@stackbit/dev-common/dist/services/socket-service';
|
|
13
|
+
|
|
14
|
+
export async function start({
|
|
15
|
+
serverPort,
|
|
16
|
+
ssgPort,
|
|
17
|
+
ssgHost,
|
|
18
|
+
rootDir,
|
|
19
|
+
runnableDir
|
|
20
|
+
}: {
|
|
21
|
+
serverPort: number;
|
|
22
|
+
ssgPort: number;
|
|
23
|
+
ssgHost: string;
|
|
24
|
+
rootDir: string;
|
|
25
|
+
runnableDir?: string;
|
|
26
|
+
}) {
|
|
27
|
+
const server = express();
|
|
28
|
+
const httpServer = http.createServer(server);
|
|
29
|
+
socketService.use(httpServer, logger);
|
|
30
|
+
|
|
31
|
+
const runner = new Runner({
|
|
32
|
+
rootDir,
|
|
33
|
+
runnableDir
|
|
34
|
+
});
|
|
35
|
+
await runner.install();
|
|
36
|
+
|
|
37
|
+
server.use(require('cors')());
|
|
38
|
+
// if (config.setContentSecurityPolicy) {
|
|
39
|
+
// server.use((req, res, next) => {
|
|
40
|
+
// const hosts = getClientAllowedHosts(req).join(' ');
|
|
41
|
+
// res.set('Content-Security-Policy', `frame-ancestors 'self' ${hosts}`);
|
|
42
|
+
// next();
|
|
43
|
+
// });
|
|
44
|
+
// }
|
|
45
|
+
|
|
46
|
+
// log requests
|
|
47
|
+
server.use((req, res, next) => {
|
|
48
|
+
logger.debug(`got request ${req.method} ${req.path}`, { method: req.method, originalUrl: req.originalUrl, url: req.url, path: req.path });
|
|
49
|
+
next();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
server.use('/_static', express.static('static'));
|
|
53
|
+
server.use(consts.STATIC_ASSETS_PUBLIC_PATH, express.static(path.join(rootDir, consts.STATIC_ASSETS_FILE_PATH)));
|
|
54
|
+
server.use(consts.STATIC_THEME_ASSETS_PUBLIC_PATH, express.static(path.join(rootDir, consts.STATIC_THEME_ASSETS_FILE_PATH)));
|
|
55
|
+
|
|
56
|
+
routes(server, runner);
|
|
57
|
+
server.use(proxy(ssgHost, ssgPort, runner));
|
|
58
|
+
|
|
59
|
+
return new Promise<void>((resolve, reject) => {
|
|
60
|
+
httpServer.listen(serverPort, () => {
|
|
61
|
+
logger.debug('Server running on port ' + serverPort);
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import express, { Request } from 'express';
|
|
4
|
+
import proxy from 'http-proxy-middleware';
|
|
5
|
+
import logger from '../services/logger';
|
|
6
|
+
import consts from '../consts';
|
|
7
|
+
import { createProxyModifyResponseMiddleware, isHtml, injectScript } from '@stackbit/dev-common/dist/utils/proxy-utils';
|
|
8
|
+
import { renderLoadingPage } from '@stackbit/dev-common/dist/utils/pages';
|
|
9
|
+
import config from '../config';
|
|
10
|
+
import Runner from '../runner';
|
|
11
|
+
|
|
12
|
+
export default function proxyMiddleware(ssgHost: string, ssgPort: number, runner: Runner) {
|
|
13
|
+
const router = express.Router();
|
|
14
|
+
|
|
15
|
+
router.use((req, res, next) => {
|
|
16
|
+
_.unset(req, 'headers.x-forwarded-proto');
|
|
17
|
+
const rawHeaders = _.get(req, 'rawHeaders');
|
|
18
|
+
const headerIndex = _.findIndex(rawHeaders, (headerValue) => headerValue.toLowerCase() === 'x-forwarded-proto');
|
|
19
|
+
if (headerIndex > -1) {
|
|
20
|
+
// remove the header key and value
|
|
21
|
+
rawHeaders.splice(headerIndex, 2);
|
|
22
|
+
}
|
|
23
|
+
next();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const proxyTargetUrl = `http://${ssgHost}:${ssgPort}`;
|
|
27
|
+
|
|
28
|
+
const handlerProxyError = (err: any, req: any, res: any) => {
|
|
29
|
+
if (err?.code === 'ECONNREFUSED' && res?.status) {
|
|
30
|
+
renderLoadingPage(req, res, {
|
|
31
|
+
assetsDir: path.join(__dirname, '../assets'),
|
|
32
|
+
proxyUrl: proxyTargetUrl,
|
|
33
|
+
proxyPath: req?.path || '',
|
|
34
|
+
htmlMessage: '',
|
|
35
|
+
status: 504,
|
|
36
|
+
statusMessage: 'aa'
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// visit https://github.com/chimurai/http-proxy-middleware#context-matching
|
|
42
|
+
// for docs on how path matching works, it is different from express matching
|
|
43
|
+
const directPaths = _.uniq(_.concat(consts.DEFAULT_DIRECT_PROXY_PATH, runner.getDirectPaths()));
|
|
44
|
+
router.use(
|
|
45
|
+
proxy(directPaths, {
|
|
46
|
+
target: proxyTargetUrl,
|
|
47
|
+
changeOrigin: runner.getDirectChangeOrigin(),
|
|
48
|
+
ws: true,
|
|
49
|
+
logProvider: () => logger,
|
|
50
|
+
logLevel: config.logLevel === 'debug' ? 'info' : 'silent',
|
|
51
|
+
router: runner.getDirectRoutes() ?? [],
|
|
52
|
+
onError: (err: any, req: any, res: any) => {
|
|
53
|
+
logger.debug(`onError in direct proxy, request: ${req.method} ${req.url}, error: ${err.message}`, { error: err });
|
|
54
|
+
handlerProxyError(err, req, res);
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
router.use(
|
|
60
|
+
proxy({
|
|
61
|
+
target: proxyTargetUrl,
|
|
62
|
+
changeOrigin: true,
|
|
63
|
+
followRedirects: true,
|
|
64
|
+
logProvider: () => logger,
|
|
65
|
+
logLevel: config.logLevel === 'debug' ? 'info' : 'silent',
|
|
66
|
+
onError: (err: any, req: any, res: any) => {
|
|
67
|
+
logger.debug(`onError in main proxy, request: ${req.method} ${req.url}, error: ${err.message}`, { error: err });
|
|
68
|
+
handlerProxyError(err, req, res);
|
|
69
|
+
},
|
|
70
|
+
selfHandleResponse: true,
|
|
71
|
+
onProxyRes: createProxyModifyResponseMiddleware({
|
|
72
|
+
logTagName: 'proxy',
|
|
73
|
+
shouldModifyBody: (proxyRes: Request, req: Request) => {
|
|
74
|
+
return req.method === 'GET' && isHtml(proxyRes);
|
|
75
|
+
},
|
|
76
|
+
modifyBody: (body: string) => {
|
|
77
|
+
body = injectScript(body, config.snippetUrl);
|
|
78
|
+
return body;
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return router;
|
|
85
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { Request, Response, NextFunction, Router } from 'express';
|
|
3
|
+
import bodyParser from 'body-parser';
|
|
4
|
+
import Runner from '../runner';
|
|
5
|
+
import logger from '../services/logger';
|
|
6
|
+
import { PageNotFoundError } from '@stackbit/dev-common/dist/services/response-errors';
|
|
7
|
+
|
|
8
|
+
export default function routes(router: Router, runner: Runner) {
|
|
9
|
+
router.get('/_health', (req: Request, res: Response) => {
|
|
10
|
+
return res.status(200).json({ status: 'ok' });
|
|
11
|
+
});
|
|
12
|
+
router.post('/_objects', [bodyParser.json({ limit: '500kb' })], (req: Request, res: Response, next: NextFunction) => {
|
|
13
|
+
const { objectIds } = req.body;
|
|
14
|
+
return runner
|
|
15
|
+
.getObjects(objectIds)
|
|
16
|
+
.then((result) => {
|
|
17
|
+
res.json(result);
|
|
18
|
+
})
|
|
19
|
+
.catch((err) => {
|
|
20
|
+
logger.error('Error fetching data objects');
|
|
21
|
+
logger.debug('error getting fieldData from runner', { error: err.message || err });
|
|
22
|
+
if (_.has(err, 'stack')) {
|
|
23
|
+
logger.debug('error getting fieldData from runner, error.stack: ' + err.stack);
|
|
24
|
+
}
|
|
25
|
+
res.status(500).send('Error');
|
|
26
|
+
next(err);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
router.post('/_objectsWithAnnotations', [bodyParser.json({ limit: '500kb' })], (req: Request, res: Response, next: NextFunction) => {
|
|
30
|
+
const { annotationTree, clientAnnotationErrors } = req.body;
|
|
31
|
+
return runner
|
|
32
|
+
.getObjectsWithAnnotations(annotationTree, clientAnnotationErrors)
|
|
33
|
+
.then((result) => {
|
|
34
|
+
res.json(result);
|
|
35
|
+
})
|
|
36
|
+
.catch((err) => {
|
|
37
|
+
logger.error('Error fetching data objects with annotations');
|
|
38
|
+
logger.debug('error getting fieldData with annotations from runner', { error: err.message || err });
|
|
39
|
+
if (_.has(err, 'stack')) {
|
|
40
|
+
logger.debug('error getting fieldData with annotations from runner, error.stack: ' + err.stack);
|
|
41
|
+
}
|
|
42
|
+
res.status(500).send('Error');
|
|
43
|
+
next(err);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
router.get('/_collections', (req: Request, res: Response, next: NextFunction) => {
|
|
47
|
+
return runner
|
|
48
|
+
.getCollections()
|
|
49
|
+
.then((result) => {
|
|
50
|
+
res.json(result);
|
|
51
|
+
})
|
|
52
|
+
.catch((err) => {
|
|
53
|
+
logger.error('Error fetching collections');
|
|
54
|
+
logger.debug('error getting collections from runner', { error: err.message || err });
|
|
55
|
+
if (_.has(err, 'stack')) {
|
|
56
|
+
logger.debug('error getting collections from runner, error.stack: ' + err.stack);
|
|
57
|
+
}
|
|
58
|
+
res.status(500).send('Error');
|
|
59
|
+
next(err);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
router.get('/_sitemap', (req: Request, res: Response, next: NextFunction) => {
|
|
63
|
+
return runner
|
|
64
|
+
.getSiteMap()
|
|
65
|
+
.then((result) => {
|
|
66
|
+
res.json(result);
|
|
67
|
+
})
|
|
68
|
+
.catch((err) => {
|
|
69
|
+
logger.error('Error fetching sitemap');
|
|
70
|
+
logger.debug('error getting sitemap from runner', { error: err.message || err });
|
|
71
|
+
if (_.has(err, 'stack')) {
|
|
72
|
+
logger.debug('error getting sitemap from runner, error.stack: ' + err.stack);
|
|
73
|
+
}
|
|
74
|
+
res.status(500).send('Error');
|
|
75
|
+
next(err);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
router.get('/_schema', (req: Request, res: Response, next: NextFunction) => {
|
|
79
|
+
return runner
|
|
80
|
+
.getSchema()
|
|
81
|
+
.then((schema) => res.json(schema))
|
|
82
|
+
.catch((err) => {
|
|
83
|
+
logger.error('Error fetching schema');
|
|
84
|
+
logger.debug('error getting schema from runner', { error: err });
|
|
85
|
+
res.status(500).send('Error');
|
|
86
|
+
next(err);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
router.get('/_pageUrl', (req: Request, res: Response, next: NextFunction) => {
|
|
90
|
+
const { entryId: objectId, spaceId: projectId, locale } = req.query;
|
|
91
|
+
runner
|
|
92
|
+
.getUrl(objectId as string, projectId as string, locale as string)
|
|
93
|
+
.then((url) => res.json({ url }))
|
|
94
|
+
.catch((err) => {
|
|
95
|
+
if (err !== PageNotFoundError) {
|
|
96
|
+
logger.error('Error querying for page url');
|
|
97
|
+
}
|
|
98
|
+
const log = err === PageNotFoundError ? logger.warn : logger.debug;
|
|
99
|
+
log('error getting page path from runner', { params: req.query, error: err });
|
|
100
|
+
res.json({ error: err.toString() });
|
|
101
|
+
next(err);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
router.get('/_object', (req: Request, res: Response, next: NextFunction) => {
|
|
105
|
+
const { objectId, projectId } = req.query;
|
|
106
|
+
return runner
|
|
107
|
+
.getObject(objectId as string, projectId as string)
|
|
108
|
+
.then((response) => {
|
|
109
|
+
res.json(response);
|
|
110
|
+
})
|
|
111
|
+
.catch((err) => {
|
|
112
|
+
logger.error('Error fetching object');
|
|
113
|
+
logger.debug('error getting object', { error: err });
|
|
114
|
+
next(err);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
router.post('/_object', [bodyParser.json({ limit: '500kb' })], (req: Request, res: Response, next: NextFunction) => {
|
|
118
|
+
const { modelName, filePath, object, user } = req.body;
|
|
119
|
+
return runner
|
|
120
|
+
.createObject(modelName, filePath, object, user)
|
|
121
|
+
.then((response) => {
|
|
122
|
+
res.json(response);
|
|
123
|
+
})
|
|
124
|
+
.catch((err) => {
|
|
125
|
+
logger.error('Error creating object');
|
|
126
|
+
logger.debug('error creating object', { error: err });
|
|
127
|
+
next(err);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
router.put('/_object', [bodyParser.json({ limit: '500kb' })], (req: Request, res: Response, next: NextFunction) => {
|
|
131
|
+
const { objectId, projectId } = req.query;
|
|
132
|
+
const { object, user } = req.body;
|
|
133
|
+
return runner
|
|
134
|
+
.updateObject(objectId as string, projectId as string, object, user)
|
|
135
|
+
.then((response) => {
|
|
136
|
+
res.json(response);
|
|
137
|
+
})
|
|
138
|
+
.catch((err) => {
|
|
139
|
+
logger.error('Error updating object');
|
|
140
|
+
logger.debug('error updating object', { error: err });
|
|
141
|
+
next(err);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
router.delete('/_object', [bodyParser.json({ limit: '500kb' })], (req: Request, res: Response, next: NextFunction) => {
|
|
145
|
+
const { objectId } = req.query;
|
|
146
|
+
const { user } = req.body;
|
|
147
|
+
return runner
|
|
148
|
+
.deleteObject(objectId as string, user)
|
|
149
|
+
.then((response) => {
|
|
150
|
+
res.json(response);
|
|
151
|
+
})
|
|
152
|
+
.catch((err) => {
|
|
153
|
+
logger.error('Error deleting collections');
|
|
154
|
+
logger.debug('error deleting object', { error: err });
|
|
155
|
+
next(err);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
router.get('/_assets', [bodyParser.json({ limit: '500kb' })], (req: Request, res: Response, next: NextFunction) => {
|
|
159
|
+
const filterParams = _.pick(req.body, ['searchQuery', 'pageId', 'pageSize']);
|
|
160
|
+
return runner
|
|
161
|
+
.listAssets(filterParams)
|
|
162
|
+
.then((response) => {
|
|
163
|
+
res.json(response);
|
|
164
|
+
})
|
|
165
|
+
.catch((err) => {
|
|
166
|
+
logger.error('Error listing assets');
|
|
167
|
+
logger.debug('error listing assets', { error: err });
|
|
168
|
+
res.status(500).json({ error: err.toString() });
|
|
169
|
+
next(err);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
router.post('/_assets', [bodyParser.json({ limit: '500kb' })], (req: Request, res: Response, next: NextFunction) => {
|
|
173
|
+
const { url, filename, user } = req.body;
|
|
174
|
+
return runner
|
|
175
|
+
.uploadAsset(url, filename, user)
|
|
176
|
+
.then((response) => {
|
|
177
|
+
res.json(response);
|
|
178
|
+
})
|
|
179
|
+
.catch((err) => {
|
|
180
|
+
logger.error('Error adding asset');
|
|
181
|
+
logger.debug('error uploading asset', { error: err });
|
|
182
|
+
res.status(500).json({ error: err.toString() });
|
|
183
|
+
next(err);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import chokidar from 'chokidar';
|
|
3
|
+
|
|
4
|
+
export function watchDir(dir: string, onFileChange: (filePaths: string[]) => void, throttleDelay = 1000) {
|
|
5
|
+
const watcher = chokidar.watch('.', {
|
|
6
|
+
cwd: dir,
|
|
7
|
+
ignored: (filePath) =>
|
|
8
|
+
filePath.includes('/.git/') ||
|
|
9
|
+
filePath.includes('/.next/') ||
|
|
10
|
+
filePath.includes('/.cache/') ||
|
|
11
|
+
filePath.includes('/node_modules/') ||
|
|
12
|
+
(filePath.includes('/.') && !filePath.includes('/.stackbit/')),
|
|
13
|
+
persistent: true,
|
|
14
|
+
ignoreInitial: true
|
|
15
|
+
});
|
|
16
|
+
let changedFiles: string[] = [];
|
|
17
|
+
const throttledFileChange = _.throttle(() => {
|
|
18
|
+
if (changedFiles.length) {
|
|
19
|
+
onFileChange(changedFiles);
|
|
20
|
+
changedFiles = [];
|
|
21
|
+
}
|
|
22
|
+
}, throttleDelay);
|
|
23
|
+
const handleFileChange = (filePath: string) => {
|
|
24
|
+
if (!changedFiles.includes(filePath)) {
|
|
25
|
+
changedFiles.push(filePath);
|
|
26
|
+
}
|
|
27
|
+
throttledFileChange();
|
|
28
|
+
};
|
|
29
|
+
watcher
|
|
30
|
+
.on('add', handleFileChange)
|
|
31
|
+
.on('change', handleFileChange)
|
|
32
|
+
.on('unlink', handleFileChange)
|
|
33
|
+
.on('addDir', handleFileChange)
|
|
34
|
+
.on('unlinkDir', handleFileChange);
|
|
35
|
+
|
|
36
|
+
return watcher;
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
import config from '../config';
|
|
3
|
+
|
|
4
|
+
const defaultFormats = [winston.format.colorize(), winston.format.simple()];
|
|
5
|
+
|
|
6
|
+
const transports = [
|
|
7
|
+
new winston.transports.Console({
|
|
8
|
+
format: winston.format.combine(...defaultFormats)
|
|
9
|
+
})
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const logger = winston.createLogger({
|
|
13
|
+
level: config.logLevel,
|
|
14
|
+
transports: transports
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const createLogger = ({ label }: { label: string }) => {
|
|
18
|
+
const formats = defaultFormats.slice();
|
|
19
|
+
if (label) {
|
|
20
|
+
formats.push(
|
|
21
|
+
winston.format.label({
|
|
22
|
+
label: label,
|
|
23
|
+
message: true
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return winston.createLogger({
|
|
28
|
+
level: config.logLevel,
|
|
29
|
+
format: winston.format.combine(...formats),
|
|
30
|
+
transports: transports
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default logger;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Configstore from 'configstore';
|
|
2
|
+
import uuid from 'uuid';
|
|
3
|
+
|
|
4
|
+
const config = new Configstore(
|
|
5
|
+
'stackbit-dev',
|
|
6
|
+
{
|
|
7
|
+
userId: uuid.v4().toString().replace(/\-/g, '')
|
|
8
|
+
},
|
|
9
|
+
{ globalConfigPath: true }
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export function getUserId() {
|
|
13
|
+
return config.get('userId');
|
|
14
|
+
}
|