@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.
Files changed (60) hide show
  1. package/dist/assets/loading-page.html +54 -0
  2. package/dist/config.d.ts +7 -0
  3. package/dist/config.js +9 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/consts.d.ts +12 -0
  6. package/dist/consts.js +47 -0
  7. package/dist/consts.js.map +1 -0
  8. package/dist/dev.d.ts +6 -0
  9. package/dist/dev.js +57 -0
  10. package/dist/dev.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +46 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/runner/index.d.ts +38 -0
  15. package/dist/runner/index.js +141 -0
  16. package/dist/runner/index.js.map +1 -0
  17. package/dist/runner/ssg.d.ts +9 -0
  18. package/dist/runner/ssg.js +15 -0
  19. package/dist/runner/ssg.js.map +1 -0
  20. package/dist/server/index.d.ts +7 -0
  21. package/dist/server/index.js +51 -0
  22. package/dist/server/index.js.map +1 -0
  23. package/dist/server/proxy.d.ts +2 -0
  24. package/dist/server/proxy.js +81 -0
  25. package/dist/server/proxy.js.map +1 -0
  26. package/dist/server/routes.d.ts +3 -0
  27. package/dist/server/routes.js +190 -0
  28. package/dist/server/routes.js.map +1 -0
  29. package/dist/services/api.d.ts +4 -0
  30. package/dist/services/api.js +15 -0
  31. package/dist/services/api.js.map +1 -0
  32. package/dist/services/file-watcher.d.ts +2 -0
  33. package/dist/services/file-watcher.js +42 -0
  34. package/dist/services/file-watcher.js.map +1 -0
  35. package/dist/services/logger.d.ts +6 -0
  36. package/dist/services/logger.js +35 -0
  37. package/dist/services/logger.js.map +1 -0
  38. package/dist/services/ngrok.d.ts +1 -0
  39. package/dist/services/ngrok.js +18 -0
  40. package/dist/services/ngrok.js.map +1 -0
  41. package/dist/services/user-config.d.ts +1 -0
  42. package/dist/services/user-config.js +16 -0
  43. package/dist/services/user-config.js.map +1 -0
  44. package/package.json +59 -0
  45. package/src/assets/loading-page.html +54 -0
  46. package/src/config.ts +6 -0
  47. package/src/consts.ts +44 -0
  48. package/src/dev.ts +65 -0
  49. package/src/index.ts +48 -0
  50. package/src/runner/index.ts +162 -0
  51. package/src/runner/ssg.ts +10 -0
  52. package/src/server/index.ts +65 -0
  53. package/src/server/proxy.ts +85 -0
  54. package/src/server/routes.ts +186 -0
  55. package/src/services/api.ts +10 -0
  56. package/src/services/file-watcher.ts +37 -0
  57. package/src/services/logger.ts +34 -0
  58. package/src/services/ngrok.ts +11 -0
  59. package/src/services/user-config.ts +14 -0
  60. 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,10 @@
1
+ import axios from 'axios';
2
+ import config from '../config';
3
+
4
+ export default {
5
+ updateLocalDev: (userId: string, localDevConfig: any) => {
6
+ return axios.post(`${config.apiUrl}/project/local/${userId}`, {
7
+ config: localDevConfig
8
+ });
9
+ }
10
+ };
@@ -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,11 @@
1
+ import ngrok from 'ngrok';
2
+
3
+ export async function createTunnel(port: number, logger: any) {
4
+ return ngrok.connect({
5
+ addr: port,
6
+ onStatusChange: (status) => {
7
+ logger?.debug('Tunnel status: ' + status);
8
+ },
9
+ onLogEvent: (data) => {}
10
+ });
11
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ declare module 'http-proxy-middleware' {
2
+ export default function proxy(options: any, options2?: any): any;
3
+ }