thunderous-server 0.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jonathan DeWitt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # Thunderous Server
2
+
3
+ > [!CAUTION]
4
+ > This project is experimental. It is not ready for production use at this time.
5
+
6
+ **Thunderous Server is a web framework and static generator designed to support and supplement _plain old HTML_.**
7
+
8
+ **What it does NOT do:**
9
+
10
+ - No special file extensions
11
+ - No unusual mixed syntax
12
+ - No complex route config
13
+ - No compiler magic
14
+
15
+ **What it DOES:**
16
+
17
+ - HTML is enhanced to support server-side templating and layout composition.
18
+ - TypeScript is compiled for browser runtimes
19
+ - NPM dependencies are extracted to static assets and referenced via import maps
20
+
21
+ All you need to set up your app is a source folder (`src` by default) with an `index.html` file. Routing is achieved natively, through the folder structure
22
+
23
+ ## Examples
24
+
25
+ **\_layout.html**:
26
+
27
+ ```html
28
+ <!--
29
+ Since this file begins with an underscore, it will not be included
30
+ in the static output. Its contents may be used by other HTML files
31
+ that reference it, but it will never be served directly.
32
+ -->
33
+ <html>
34
+ <head>
35
+ <title>Thunderous Server</title>
36
+ </head>
37
+ <body>
38
+ <!--
39
+ <slot> tags are usually reserved for shadow DOM, but they are
40
+ appropriate here since they serve a similar purpose. Any HTML
41
+ file that includes this layout will insert its content into this
42
+ slot.
43
+ -->
44
+ <slot></slot>
45
+ </body>
46
+ </html>
47
+ ```
48
+
49
+ **index.html**:
50
+
51
+ ```html
52
+ <!--
53
+ This is a "processing instruction", and there's already support
54
+ in XML and the DOM spec. Browsers just parse them as comments,
55
+ but Thunderous Server will strip this away before it reaches
56
+ the browser anyway.
57
+ -->
58
+ <?layout href="_layout.html">
59
+
60
+ <ul>
61
+ <!--
62
+ This is evaluated in-place and replaced with the result.
63
+ This is server-side only; you'll never see <script expr>
64
+ in the source markup served to the browser.
65
+ -->
66
+ <script expr>
67
+ list.map((item) => html` <li>test ${label} - ${item}</li> `);
68
+ </script>
69
+ </ul>
70
+
71
+ <!--
72
+ Thunderous supports SSR web components by rendering declarative
73
+ shadow DOM in the source markup sent to the browser. When
74
+ MyComponent.define() is called on the server, it will inject
75
+ this element with <my-component>'s rendered output.
76
+ -->
77
+ <my-component></my-component>
78
+
79
+ <!--
80
+ This is ordinary client-only code that remains in the source markup
81
+ sent to the browser.
82
+ -->
83
+ <script>
84
+ console.log('Hello from the browser!');
85
+ </script>
86
+
87
+ <!--
88
+ This is also client-only. Client-side modules are processed to
89
+ "vendorize" NPM dependencies. That is, imports are ejected as
90
+ assets the browser can see and use. This does NOT bundle; instead,
91
+ it generates an import map. It will only vendorize the dependencies
92
+ that are actually imported.
93
+ -->
94
+ <script type="module">
95
+ console.log('Hello from a module!');
96
+ </script>
97
+
98
+ <!--
99
+ This runs on both the client and server. Isomorphic scripts are
100
+ always type="module". Since it runs on the client, it will also
101
+ vendorize NPM dependencies.
102
+ -->
103
+ <script isomorphic>
104
+ import { MyComponent } from './scripts';
105
+ MyComponent.define('my-component');
106
+ </script>
107
+
108
+ <!--
109
+ This runs on the server only. Server scripts are always modules.
110
+ Exported values are available in <script expr> tags.
111
+ -->
112
+ <script server>
113
+ export default {
114
+ label: 'Item',
115
+ list: [1, 2, 3, 4, 5],
116
+ };
117
+ </script>
118
+ ```
119
+
120
+ ## TODO
121
+
122
+ - [ ] Create (or find existing) extension that supports:
123
+ - [ ] TypeScript inside `<script>` tags
124
+ - [ ] IDE navigation in `<script server>` and `<script expr>` tags (go to definition, find references, etc.)
125
+ - [ ] Auto-imports for `<script server>`, `<script isomorphic>`, and `<script type="module">`
126
+ - [ ] Lint against multiple `<script server>` tags to avoid export collisions
127
+ - [ ] Typechecking to ensure the default export in `<script server>` is `Record<PropertyKey, unknown>`
128
+ - [ ] OPTIONAL: Lint and autofix for trailing `;` in `<script expr>` tags
129
+ _e.g., `('hello';)` is an invalid expression, so `<script expr>'hello';</script>` is technically incorrect. That said, Thunderous Server does handle this currently by stripping it from the content before it's evaluated._
package/bin/thunderous ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Register tsx loaders in-process (no child process spawn needed)
4
+ import { register as registerEsm } from 'tsx/esm/api';
5
+ import { register as registerCjs } from 'tsx/cjs/api';
6
+ registerCjs();
7
+ registerEsm();
8
+
9
+ // Dynamically import the TypeScript CLI entry point
10
+ await import('../src/cli.ts');
package/dist/index.cjs ADDED
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ escapeHtml: () => escapeHtml,
24
+ getMeta: () => getMeta,
25
+ raw: () => raw
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/meta.ts
30
+ var metaState = {
31
+ config: {
32
+ name: "",
33
+ baseDir: "",
34
+ outDir: ""
35
+ },
36
+ pathname: "/",
37
+ breadcrumbs: [],
38
+ title: "",
39
+ name: "",
40
+ filename: ""
41
+ };
42
+ var getMeta = () => {
43
+ return Object.freeze({
44
+ ...metaState,
45
+ config: { ...metaState.config },
46
+ breadcrumbs: [...metaState.breadcrumbs]
47
+ });
48
+ };
49
+
50
+ // src/utilities.ts
51
+ var escapeHtml = (str) => {
52
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
53
+ };
54
+ var RawHtml = class {
55
+ value;
56
+ constructor(value) {
57
+ this.value = value;
58
+ }
59
+ toString() {
60
+ return this.value;
61
+ }
62
+ };
63
+ var raw = (str) => new RawHtml(str);
64
+ // Annotate the CommonJS export names for ESM import in node:
65
+ 0 && (module.exports = {
66
+ escapeHtml,
67
+ getMeta,
68
+ raw
69
+ });
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The type for the default export from `thunderous.config.ts`.
3
+ */
4
+ type ThunderousConfig = {
5
+ /**
6
+ * The name of the site.
7
+ */
8
+ name: string;
9
+ /**
10
+ * The base directory where HTML files and static assets are served.
11
+ */
12
+ baseDir: string;
13
+ /**
14
+ * The output directory for builds.
15
+ */
16
+ outDir: string;
17
+ };
18
+
19
+ /**
20
+ * A breadcrumb (name and path) for a given page.
21
+ */
22
+ type Breadcrumb = {
23
+ /**
24
+ * The name of the page.
25
+ */
26
+ name: string;
27
+ /**
28
+ * The full URL path to the page.
29
+ */
30
+ pathname: string;
31
+ };
32
+ /**
33
+ * Metadata about the current page being rendered.
34
+ */
35
+ type Meta = {
36
+ /**
37
+ * The configuration object from `thunderous.config.ts`.
38
+ */
39
+ config: ThunderousConfig;
40
+ /**
41
+ * The pathname of the current page being rendered.
42
+ */
43
+ pathname: string;
44
+ /**
45
+ * The breadcrumbs of the current page being rendered.
46
+ */
47
+ breadcrumbs: Breadcrumb[];
48
+ /**
49
+ * The inferred title of the current page being rendered.
50
+ *
51
+ * This is derived from the filename, replacing hyphens and underscores
52
+ * with spaces, and capitalizing the first letter of each word.
53
+ */
54
+ title: string;
55
+ /**
56
+ * The name of the current page being rendered.
57
+ *
58
+ * This is the filename without the extension.
59
+ */
60
+ name: string;
61
+ /**
62
+ * The filename of the current page being rendered.
63
+ *
64
+ * This is the full filename including the extension.
65
+ */
66
+ filename: string;
67
+ };
68
+ /** Get metadata about the current page being rendered. */
69
+ declare const getMeta: () => Meta;
70
+
71
+ /**
72
+ * Escape a string for safe insertion into HTML.
73
+ */
74
+ declare const escapeHtml: (str: string) => string;
75
+ declare class RawHtml {
76
+ value: string;
77
+ constructor(value: string);
78
+ toString(): string;
79
+ }
80
+ /**
81
+ * Wrap a string so it bypasses automatic HTML escaping in `<script expr>`.
82
+ */
83
+ declare const raw: (str: string) => RawHtml;
84
+
85
+ export { escapeHtml, getMeta, raw };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The type for the default export from `thunderous.config.ts`.
3
+ */
4
+ type ThunderousConfig = {
5
+ /**
6
+ * The name of the site.
7
+ */
8
+ name: string;
9
+ /**
10
+ * The base directory where HTML files and static assets are served.
11
+ */
12
+ baseDir: string;
13
+ /**
14
+ * The output directory for builds.
15
+ */
16
+ outDir: string;
17
+ };
18
+
19
+ /**
20
+ * A breadcrumb (name and path) for a given page.
21
+ */
22
+ type Breadcrumb = {
23
+ /**
24
+ * The name of the page.
25
+ */
26
+ name: string;
27
+ /**
28
+ * The full URL path to the page.
29
+ */
30
+ pathname: string;
31
+ };
32
+ /**
33
+ * Metadata about the current page being rendered.
34
+ */
35
+ type Meta = {
36
+ /**
37
+ * The configuration object from `thunderous.config.ts`.
38
+ */
39
+ config: ThunderousConfig;
40
+ /**
41
+ * The pathname of the current page being rendered.
42
+ */
43
+ pathname: string;
44
+ /**
45
+ * The breadcrumbs of the current page being rendered.
46
+ */
47
+ breadcrumbs: Breadcrumb[];
48
+ /**
49
+ * The inferred title of the current page being rendered.
50
+ *
51
+ * This is derived from the filename, replacing hyphens and underscores
52
+ * with spaces, and capitalizing the first letter of each word.
53
+ */
54
+ title: string;
55
+ /**
56
+ * The name of the current page being rendered.
57
+ *
58
+ * This is the filename without the extension.
59
+ */
60
+ name: string;
61
+ /**
62
+ * The filename of the current page being rendered.
63
+ *
64
+ * This is the full filename including the extension.
65
+ */
66
+ filename: string;
67
+ };
68
+ /** Get metadata about the current page being rendered. */
69
+ declare const getMeta: () => Meta;
70
+
71
+ /**
72
+ * Escape a string for safe insertion into HTML.
73
+ */
74
+ declare const escapeHtml: (str: string) => string;
75
+ declare class RawHtml {
76
+ value: string;
77
+ constructor(value: string);
78
+ toString(): string;
79
+ }
80
+ /**
81
+ * Wrap a string so it bypasses automatic HTML escaping in `<script expr>`.
82
+ */
83
+ declare const raw: (str: string) => RawHtml;
84
+
85
+ export { escapeHtml, getMeta, raw };
package/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ // src/meta.ts
2
+ var metaState = {
3
+ config: {
4
+ name: "",
5
+ baseDir: "",
6
+ outDir: ""
7
+ },
8
+ pathname: "/",
9
+ breadcrumbs: [],
10
+ title: "",
11
+ name: "",
12
+ filename: ""
13
+ };
14
+ var getMeta = () => {
15
+ return Object.freeze({
16
+ ...metaState,
17
+ config: { ...metaState.config },
18
+ breadcrumbs: [...metaState.breadcrumbs]
19
+ });
20
+ };
21
+
22
+ // src/utilities.ts
23
+ var escapeHtml = (str) => {
24
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
25
+ };
26
+ var RawHtml = class {
27
+ value;
28
+ constructor(value) {
29
+ this.value = value;
30
+ }
31
+ toString() {
32
+ return this.value;
33
+ }
34
+ };
35
+ var raw = (str) => new RawHtml(str);
36
+ export {
37
+ escapeHtml,
38
+ getMeta,
39
+ raw
40
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "thunderous-server",
3
+ "version": "0.0.0",
4
+ "description": "A simple server to enhance Thunderous components with SSR.",
5
+ "license": "MIT",
6
+ "author": "Jonathan DeWitt <jon.dewitt@thunder.solutions>",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "bin": {
12
+ "thunderous": "./bin/thunderous"
13
+ },
14
+ "files": [
15
+ "/dist",
16
+ "/bin",
17
+ "/src",
18
+ "/types",
19
+ "/package.json",
20
+ "/README.md"
21
+ ],
22
+ "devDependencies": {
23
+ "@eslint/css": "^0.13.0",
24
+ "@eslint/js": "^9.38.0",
25
+ "@eslint/json": "^0.13.2",
26
+ "@total-typescript/ts-reset": "^0.6.1",
27
+ "@types/connect-livereload": "^0.6.3",
28
+ "@types/express": "^5.0.3",
29
+ "@types/livereload": "^0.9.5",
30
+ "@types/node": "^24.9.1",
31
+ "@types/resolve": "^1.20.6",
32
+ "@typescript-eslint/eslint-plugin": "^8.46.2",
33
+ "connect-livereload": "^0.6.1",
34
+ "eslint": "^9.38.0",
35
+ "livereload": "^0.10.3",
36
+ "prettier": "^3.6.2",
37
+ "serve": "^14.2.5",
38
+ "tsx": "^4.20.6",
39
+ "typescript": "^5.9.3",
40
+ "typescript-eslint": "^8.46.2",
41
+ "undici-types": "^7.16.0"
42
+ },
43
+ "dependencies": {
44
+ "es-module-lexer": "^1.7.0",
45
+ "express": "^5.1.0",
46
+ "resolve": "^1.22.11",
47
+ "resolve.exports": "^2.0.3"
48
+ },
49
+ "peerDependencies": {
50
+ "thunderous": ">=2.4.1"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup --no-clean",
54
+ "demo": "cd demo && npm run",
55
+ "demo:install": "cd demo && npm install",
56
+ "lint": "eslint .",
57
+ "lint:fix": "eslint . --fix",
58
+ "format": "prettier --check . --ignore-path ../../.gitignore",
59
+ "format:fix": "prettier --write . --ignore-path ../../.gitignore",
60
+ "typecheck": "tsc --noEmit",
61
+ "test": "echo \"Error: no test specified\" && exit 1"
62
+ }
63
+ }
package/src/build.ts ADDED
@@ -0,0 +1,134 @@
1
+ import { cpSync, existsSync, rmSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
2
+ import { join, resolve, dirname, relative } from 'path';
3
+ import {
4
+ bootstrapThunderous,
5
+ processFiles,
6
+ generateStaticTemplate,
7
+ transpileTsFile,
8
+ generateImportMap,
9
+ injectImportMap,
10
+ } from './generate';
11
+ import { config } from './config';
12
+
13
+ /** Make the directory if it does not exist. Not worth installing fs-extra just for this. */
14
+ export const ensureDirSync = (dirPath: string) => {
15
+ if (!existsSync(dirPath)) {
16
+ mkdirSync(dirPath, { recursive: true });
17
+ }
18
+ };
19
+
20
+ /**
21
+ * Build the static site.
22
+ */
23
+ export const build = () => {
24
+ console.log('\x1b[36m🔨 Building static site...\x1b[0m\n');
25
+
26
+ const baseDir = resolve(config.baseDir);
27
+ const outDir = resolve(config.outDir);
28
+
29
+ if (!existsSync(baseDir)) {
30
+ console.error(`\x1b[31mError: Base directory "${baseDir}" does not exist.\x1b[0m`);
31
+ process.exit(1);
32
+ }
33
+
34
+ // Clean and create output directory
35
+ if (existsSync(outDir)) {
36
+ rmSync(outDir, { recursive: true });
37
+ }
38
+ ensureDirSync(outDir);
39
+
40
+ // Copy all files (excluding build files which we'll process separately)
41
+ console.log(`\x1b[90mCopying static assets...\x1b[0m`);
42
+
43
+ cpSync(baseDir, outDir, {
44
+ recursive: true,
45
+ filter: (src) => !/\.server\.(ts|js)$/.test(src),
46
+ });
47
+ bootstrapThunderous();
48
+
49
+ // Transpile TypeScript files FIRST and collect entry points
50
+ console.log(`\x1b[90mTranspiling TypeScript files...\x1b[0m`);
51
+ const entryFiles: string[] = [];
52
+ processFiles({
53
+ dir: outDir,
54
+ filter: (filePath) => /(?<!\.d|\.tmp|\.server)\.ts$/.test(filePath),
55
+ callback: (filePath) => {
56
+ const relPath = relative(outDir, filePath);
57
+ console.log(`\x1b[90mTranspiling: ${relPath}\x1b[0m`);
58
+ try {
59
+ const jsPath = transpileTsFile(filePath);
60
+ entryFiles.push(jsPath);
61
+ console.log(`\x1b[32m✓ Transpiled: ${relPath} → ${relative(outDir, jsPath)}\x1b[0m`);
62
+ } catch (error) {
63
+ console.error(`\x1b[31m✗ Error transpiling ${filePath}:\x1b[0m`, error);
64
+ }
65
+ },
66
+ });
67
+
68
+ // Process HTML files AFTER transpilation
69
+ console.log(`\x1b[90mProcessing HTML files...\x1b[0m`);
70
+ const htmlEntryFiles: string[] = [];
71
+ const cleanupFunctions: Array<() => void> = [];
72
+ const errors: string[] = [];
73
+ processFiles({
74
+ dir: baseDir,
75
+ filter: (filePath) => filePath.endsWith('.html') && !filePath.split('/').pop()!.startsWith('_'),
76
+ callback: (filePath) => {
77
+ console.log(`\x1b[90mProcessing: ${relative(baseDir, filePath)}\x1b[0m`);
78
+
79
+ try {
80
+ const result = generateStaticTemplate(filePath);
81
+ const markup = result.markup;
82
+
83
+ // Collect client entry files from this HTML
84
+ htmlEntryFiles.push(...result.clientEntryFiles);
85
+ cleanupFunctions.push(result.cleanup);
86
+
87
+ // Calculate the output path
88
+ const relativePath = relative(baseDir, filePath);
89
+ const outputPath = join(outDir, relativePath);
90
+
91
+ // Ensure output directory exists
92
+ ensureDirSync(dirname(outputPath));
93
+
94
+ // Write the processed HTML
95
+ writeFileSync(outputPath, markup, 'utf-8');
96
+
97
+ console.log(`\x1b[32m✓ Generated: ${relativePath}\x1b[0m`);
98
+ } catch (error) {
99
+ console.error(`\x1b[31m✗ Error processing ${filePath}:\x1b[0m`, error);
100
+ errors.push(filePath);
101
+ }
102
+ },
103
+ });
104
+
105
+ // Vendorize node modules based on collected entry files
106
+ const allEntryFiles = [...htmlEntryFiles, ...entryFiles];
107
+ if (allEntryFiles.length > 0) {
108
+ const importMapJson = generateImportMap(allEntryFiles);
109
+
110
+ // Inject import map into HTML files
111
+ processFiles({
112
+ dir: outDir,
113
+ filter: (filePath) => filePath.endsWith('.html'),
114
+ callback: (filePath) => {
115
+ let html = readFileSync(filePath, 'utf-8');
116
+ html = injectImportMap(html, importMapJson);
117
+ writeFileSync(filePath, html, 'utf-8');
118
+ },
119
+ });
120
+ console.log(`\x1b[32m✓ Import map injected into HTML files\x1b[0m`);
121
+ }
122
+
123
+ // Clean up temporary files from HTML processing
124
+ for (const cleanup of cleanupFunctions) {
125
+ cleanup();
126
+ }
127
+
128
+ if (errors.length > 0) {
129
+ console.error(`\n\x1b[31m❌ Build failed with ${errors.length} error(s).\x1b[0m`);
130
+ process.exit(1);
131
+ }
132
+
133
+ console.log(`\n\x1b[32m✅ Static site built successfully in "${outDir}"\x1b[0m`);
134
+ };
package/src/cli.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { build } from './build';
2
+ import { dev } from './dev';
3
+
4
+ const args = process.argv.slice(2);
5
+
6
+ if (args[0] === 'dev') {
7
+ try {
8
+ dev();
9
+ } catch (error) {
10
+ console.error('Failed to start server:', error);
11
+ process.exit(1);
12
+ }
13
+ } else if (args[0] === 'build') {
14
+ try {
15
+ build();
16
+ } catch (error) {
17
+ console.error('\x1b[31mBuild failed:\x1b[0m', error);
18
+ process.exit(1);
19
+ }
20
+ } else {
21
+ console.log('Usage:');
22
+ console.log(' --dev Start the development server');
23
+ console.log(' --build Build the static site');
24
+ }