noxt-cli 0.1.6

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/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # noxt-cli
2
+
3
+ A command-line interface for [Noxt](https://npmjs.com/package/noxt-server).
4
+
5
+ ## Usage
6
+
7
+ ```sh
8
+ npx noxt-cli
9
+ npx noxt-cli <recipe>
10
+ ```
11
+
12
+ ## Defaults
13
+ If you just run `npx noxt-cli`, it will try to find a `noxt.config.yaml` file in your current directory. If it finds one, it will use it. Otherwise, it will use the default config and try to guess some defaults based on common project structure.
14
+
15
+ It will assume the following:
16
+ * `views = "./views"` directory for JSX files, if it exists
17
+ * `static = "./public"` directory for static files, if it exists
18
+ * `port = 3000` port number
19
+ * `hostname = "localhost"` hostname
20
+ * `recipe = "app/main"` if `./units/main.js` exists, it will be used as the recipe
21
+ * `recipe = "noxt-dev"` fallback recipe
22
+
23
+ ## Configuration
24
+
25
+ You can override these defaults by creating a `noxt.config.yaml` file in your current directory. It should look like this:
26
+
27
+ ```yaml
28
+ views: "./views"
29
+ static: "./public"
30
+ port: 3000
31
+ hostname: "localhost"
32
+ recipe: "app/main"
33
+ ```
34
+
35
+ ## Recipes and other units
36
+
37
+ Noxt uses [MLM](https://npmjs.com/package/mlm-core) to define recipes and other units that make up the noxt stack. You can extend the [noxt](https://npmjs.com/package/noxt-server) stack by creating your own units in your `./units/` directory.
38
+
39
+ Any package names beginning with `app/` will be loaded from your `./units/` directory.
40
+
41
+ ## License
42
+ LGPL-3.0-or-later
@@ -0,0 +1,56 @@
1
+ import { fileURLToPath } from 'url';
2
+ import { dirname, join, basename, resolve } from 'path';
3
+ import {
4
+ readdir,
5
+ readFile,
6
+ writeFile,
7
+ copyFile,
8
+ mkdir,
9
+ } from 'fs/promises';
10
+ import { spawnSync } from 'child_process';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+
14
+ async function copyDir(src, dest) {
15
+ await mkdir(dest, { recursive: true });
16
+ const entries = await readdir(src, { withFileTypes: true });
17
+ for (const ent of entries) {
18
+ const s = join(src, ent.name);
19
+ const d = join(dest, ent.name);
20
+ if (ent.isDirectory()) await copyDir(s, d);
21
+ else await copyFile(s, d);
22
+ }
23
+ }
24
+
25
+ export async function execute([dir = '.']) {
26
+ const root = resolve(dir);
27
+ const files = await readdir(root).catch(() => []);
28
+ if (files.length) {
29
+ console.error('Directory is not empty, aborting.');
30
+ process.exit(1);
31
+ }
32
+
33
+ const name = basename(root);
34
+ const templateDir = join(__dirname, '..', 'starter');
35
+ await copyDir(templateDir, root);
36
+
37
+ // read the template package.json
38
+ const pkgPath = join(root, 'package.json');
39
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
40
+
41
+ // overwrite only the dynamic bits
42
+ pkg.name = name;
43
+ pkg.description = name + ': a noxt CLI starter';
44
+ pkg.private = true;
45
+ pkg.version ||= '0.0.0';
46
+ pkg.scripts ||= {};
47
+
48
+ // ensure noxt-cli is present
49
+ pkg.devDependencies ||= {};
50
+ pkg.devDependencies['noxt-cli'] = 'latest';
51
+
52
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
53
+
54
+ // install prod + peer deps
55
+ spawnSync('npm', ['install'], { cwd: root, stdio: 'inherit' });
56
+ }
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import yaml from 'js-yaml';
5
+ import { startServer } from 'noxt-server';
6
+ //import 'console-with-location';
7
+
8
+ const __dirname = path.dirname(new URL(import.meta.url).pathname);
9
+
10
+ export function execute([recipe], options) {
11
+ // read deafault config
12
+ let config = yaml.load(fs.readFileSync(path.resolve(__dirname, '../default.config.yaml'), 'utf8'));
13
+
14
+ // read local config if exists
15
+ const configPath = 'noxt.config.yaml';
16
+ const configFullPath = path.resolve(process.cwd(), configPath);
17
+
18
+ if (fs.existsSync(configFullPath)) {
19
+ console.log(`Using config file: ${configFullPath}`);
20
+ try {
21
+ const localConfig = yaml.load(fs.readFileSync(configFullPath, 'utf8'));
22
+ Object.assign(config, localConfig);
23
+ } catch (e) {
24
+ die(`Error loading config file: ${e.message}`);
25
+ }
26
+ recipe ??= config.recipe;
27
+ } else {
28
+ throw new Error(`Config file not found: ${configPath}`);
29
+ }
30
+
31
+ // override config with command line options
32
+ for (const key in options) {
33
+ if (!(key in config)) {
34
+ warn(`Unknown config option: ${key}`);
35
+ }
36
+ config[key] = options[key] ?? config[key];
37
+ }
38
+
39
+ startServer({ config, recipe }).catch(err => {
40
+ console.error(`[noxt-server] Error starting server: ${err.message}`);
41
+ console.log(`[noxt-server]`, err.stack);
42
+ process.exit(1);
43
+ });
44
+
45
+ }
46
+ function cmd_create() {
47
+ // copy the starter pack from ./starter, but only if it is empty
48
+ const starterPath = targetPath.resolve(__dirname, '../starter');
49
+ const pathArg = args.shift();
50
+ const targetPath = pathArg ? targetPath.resolve(process.cwd(), pathArg) : process.cwd();
51
+ // if not exists, create recursively
52
+ if (!fs.existsSync(targetPath)) {
53
+ fs.mkdirSync(targetPath, { recursive: true });
54
+ } else {
55
+ // if not empty, except for node_modules, refuse to overwrite
56
+ const files = fs.readdirSync(targetPath).filter(file => file !== 'node_modules');
57
+ if (files.length) {
58
+ die(`Directory ${targetPath} is not empty. Refusing to overwrite it.`);
59
+ }
60
+ }
61
+ // recursively copy starterPath to targetPath
62
+ fs.cpSync(starterPath, targetPath, { recursive: true });
63
+ console.log(`Created noxt starter pack in ${targetPath}\ncd ${targetPath}\nnpx noxt-cli`);
64
+ process.exit(0);
65
+ }
package/bin/noxt-cli ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import minimist from 'minimist';
3
+ //import 'console-with-location';
4
+
5
+ import * as cmd_start from './cmd_start.js';
6
+ import * as cmd_create from './cmd_create.js';
7
+
8
+ const commands = {
9
+ start: cmd_start,
10
+ create: cmd_create,
11
+ };
12
+
13
+ const { _: args, ...options } = minimist(process.argv.slice(2), {
14
+ string: ['views'], // treat as strings
15
+ unknown: (arg) => true
16
+ });
17
+
18
+ const cmd = args.shift() ?? 'start';
19
+
20
+ if (cmd in commands) {
21
+ commands[cmd].execute(args, options);
22
+ } else {
23
+ console.log(`Usage: noxt-cli <${Object.keys(commands).join('|')}> [options]`);
24
+ process.exit(1);
25
+ }
@@ -0,0 +1,7 @@
1
+ port: 3000
2
+ ssl: false
3
+ host: localhost
4
+ logLevel: info
5
+ views: []
6
+ static: []
7
+ context: []
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "noxt-cli",
3
+ "version": "0.1.6",
4
+ "description": "CLI starter for noxt-server",
5
+ "type": "module",
6
+ "bin": {
7
+ "noxt-cli": "./bin/noxt-cli"
8
+ },
9
+ "repository": "https://github.com/zocky/noxt-cli",
10
+ "scripts": {
11
+ "test": "echo \"No tests yet\" && exit 0"
12
+ },
13
+ "keywords": [
14
+ "express",
15
+ "jsx",
16
+ "noxt",
17
+ "server",
18
+ "ssr",
19
+ "async",
20
+ "cli"
21
+ ],
22
+ "author": "Zoran Obradović [https://github.com/zocky]",
23
+ "license": "GPL-3.0-or-later",
24
+ "dependencies": {
25
+ "js-yaml": "^4.1.0",
26
+ "minimist": "^1.2.8",
27
+ "noxt-server": "^0.1.6"
28
+ },
29
+ "devDependencies": {
30
+ "noxt-server": "../noxt-server"
31
+ },
32
+ "engines": {
33
+ "node": ">=18"
34
+ }
35
+ }
@@ -0,0 +1,9 @@
1
+ recipe: app/main
2
+ port: 3000
3
+ ssl: false
4
+ host: localhost
5
+ logLevel: info
6
+ views:
7
+ - views
8
+ static:
9
+ public
@@ -0,0 +1,14 @@
1
+ {
2
+ "type": "module",
3
+ "name": "starter",
4
+ "version": "1.0.0",
5
+ "description": "",
6
+ "scripts": {
7
+ "start": "noxt-cli start app/main",
8
+ "dev": "nodemon noxt-cli start app/main.js",
9
+ "prod": "NODE_ENV=production noxt-cli start app/main"
10
+ },
11
+ "devDependencies": {
12
+ "nodemon": "^3.0.1"
13
+ }
14
+ }
@@ -0,0 +1,31 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+ body {
5
+ margin: 0;
6
+ padding: 0;
7
+ font-family: sans-serif;
8
+ }
9
+ article {
10
+ max-width:1000px;
11
+ margin: 0 auto;
12
+ padding: 0 10px;
13
+ display: grid;
14
+ grid-template-columns: 1fr max(25%, 200px);
15
+ grid-template-areas: "header header" "main aside";
16
+ gap: 20px;
17
+
18
+ main {
19
+ grid-area:main
20
+ }
21
+
22
+ header {
23
+ grid-area:header
24
+ }
25
+
26
+ aside {
27
+ grid-area:aside
28
+ }
29
+ }
30
+
31
+
Binary file
@@ -0,0 +1 @@
1
+ console.log('Hello from noxt')
@@ -0,0 +1,52 @@
1
+ export default function Layout({ body }, { slot, Header }) {
2
+
3
+ const scripts = slot('script').map(makeScriptTag).join('\n');
4
+ const styles = slot('style').map(makeStyleTag).join('\n');
5
+ const inlineJS = slot('js').map(c => `<script>${c}</script>`).join('\n');
6
+ const inlineCSS = slot('css').map(c => `<style>${c}</style>`).join('\n');
7
+ const title=slot('title').join (' | ');
8
+
9
+ return (<>
10
+ {{ html: `<!DOCTYPE html>` }}
11
+ <html>
12
+ <head>
13
+ <meta charset="utf-8" />
14
+ <title>{title}</title>
15
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
17
+ <link href="https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,wdth,wght,GRAD@8..144,25..151,100..1000,-200..150&display=swap" rel="stylesheet" />
18
+ <link rel="stylesheet" href="/css/style.css" />
19
+ <link rel="icon" href="/img/favicon.png" />
20
+ {{ html: styles }}
21
+ {{ html: scripts }}
22
+ <script type="module" src="/js/main.js"></script>
23
+ {{ html: inlineCSS }}
24
+ </head>
25
+ <body>
26
+ <Header title={title}/>
27
+ <div class="skin layout">
28
+ {{ html: body }}
29
+ </div>
30
+ {{ html: inlineJS }}
31
+ </body>
32
+ </html>
33
+ </>);
34
+ }
35
+
36
+ function makeScriptTag(entry) {
37
+ if (typeof entry === 'string') return `<script type="module" src="${entry}"></script>`;
38
+ if (entry && typeof entry === 'object') {
39
+ const attrs = Object.entries(entry).map(([k, v]) => ` ${k}="${v}"`).join('');
40
+ return `<script${attrs}></script>`;
41
+ }
42
+ return '';
43
+ }
44
+
45
+ function makeStyleTag(entry) {
46
+ if (typeof entry === 'string') return `<link rel="stylesheet" href="${entry}">`;
47
+ if (entry && typeof entry === 'object') {
48
+ const attrs = Object.entries(entry).map(([k, v]) => ` ${k}="${v}"`).join('');
49
+ return `<link rel="stylesheet"${attrs}>`;
50
+ }
51
+ return '';
52
+ }
@@ -0,0 +1,25 @@
1
+ export const route = '/';
2
+
3
+ export default function PageIndex({ tasks }, {
4
+ // all components are available here
5
+ Json, Sidebar,
6
+ // so are request and response objects
7
+ req, res, params,
8
+ // utils are extendable by your units in ../../units
9
+ utils,
10
+ // use slot(name,value) and slot(name,key,value) to pass values to the layout
11
+ slot,
12
+ }) {
13
+ slot('title', 'Welcome to Noxt');
14
+ return (
15
+ <article>
16
+ <main>
17
+ <h2>Here are some tasks</h2>
18
+ <PageTasks.Link text="Tasks" />
19
+ </main>
20
+ <aside>
21
+ <Sidebar />
22
+ </aside>
23
+ </article>
24
+ );
25
+ }
@@ -0,0 +1,46 @@
1
+ export const route = '/tasks/:taskid';
2
+
3
+ export const fetch = {
4
+ task: ({ taskid }) => 'https://jsonplaceholder.typicode.com/todos/' + taskid
5
+ }
6
+
7
+ export default function PageIndex({ task, taskid }, {
8
+ // all components are available here
9
+ Json, Sidebar,
10
+ // so are request and response objects
11
+ req, res, params,
12
+ // utils are extendable by your units in ../../units
13
+ utils,
14
+ // use slot(name,value) and slot(name,key,value) to pass values to the layout
15
+ slot,
16
+ }) {
17
+ slot('title', 'Task ' + taskid);
18
+ return (
19
+ <article>
20
+ <main>
21
+ <h2>Here is task {taskid}</h2>
22
+ <table>
23
+ <tbody>
24
+ <tr>
25
+ <th>Id</th>
26
+ <td>{taskid}</td>
27
+ </tr>
28
+ <tr>
29
+ <th>Title</th>
30
+ <td>{task.title}</td>
31
+ </tr>
32
+ <tr>
33
+ <th>Completed</th>
34
+ <td>{task.completed}</td>
35
+ </tr>
36
+ </tbody>
37
+ </table>
38
+ <h2>Here's the data</h2>
39
+ <Json data={task} />
40
+ </main>
41
+ <aside>
42
+ <Sidebar />
43
+ </aside>
44
+ </article>
45
+ );
46
+ }
@@ -0,0 +1,48 @@
1
+ export const route = '/tasks';
2
+
3
+ export const fetch = {
4
+ tasks: 'https://jsonplaceholder.typicode.com/todos'
5
+ }
6
+
7
+ export default function PageTasks({ tasks }, {
8
+ // all components are available here
9
+ Json, PageTask, Sidebar,
10
+ // so are request and response objects
11
+ req, res, params,
12
+ // utils are extendable by your units in ../../units
13
+ utils,
14
+ // use slot(name,value) and slot(name,key,value) to pass values to the layout
15
+ slot,
16
+ }) {
17
+ slot('title', 'Welcome to Noxt');
18
+ return (
19
+ <article>
20
+ <main>
21
+ <h2>Here are some tasks</h2>
22
+ <table>
23
+ <thead>
24
+ <tr>
25
+ <th>Id</th>
26
+ <th>Title</th>
27
+ <th>Completed</th>
28
+ </tr>
29
+ </thead>
30
+ <tbody>
31
+ {tasks.map(({ id, title, completed }) => (
32
+ <tr key={id}>
33
+ <td>{id}</td>
34
+ <td><PageTasks.Link taskid={id} text={title} /></td>
35
+ <td>{completed ? 'Yes' : 'No'}</td>
36
+ </tr>
37
+ ))}
38
+ </tbody>
39
+ </table>
40
+ <h2>Here's the data</h2>
41
+ <Json data={tasks} />
42
+ </main>
43
+ <aside>
44
+ <Sidebar />
45
+ </aside>
46
+ </article>
47
+ );
48
+ }
@@ -0,0 +1,13 @@
1
+ const jsAlert = `
2
+ function doAlert(button) {
3
+ alert('Noxt says: ' + button.dataset.message);
4
+ }
5
+ `;
6
+
7
+ export default function AlertButton({ label, message }) {
8
+ return (
9
+ <Button onclick="doAlert(this)" data-message={message}>
10
+ {label}
11
+ </Button>
12
+ );
13
+ }
@@ -0,0 +1,7 @@
1
+ export default function Header({ title }) {
2
+ return (
3
+ <header>
4
+ <h1>{title}</h1>
5
+ </header>
6
+ );
7
+ }
@@ -0,0 +1,10 @@
1
+ export default function SideBar({},{ PageIndex, AlertButton }) {
2
+ return (
3
+ <nav>
4
+ <ul>
5
+ <li><PageIndex.Link text="Go Home" /></li>
6
+ <li><AlertButton label="Alert" message="Hello" /></li>
7
+ </ul>
8
+ </nav>
9
+ );
10
+ }