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 +42 -0
- package/bin/cmd_create.js +56 -0
- package/bin/cmd_start.js +65 -0
- package/bin/noxt-cli +25 -0
- package/default.config.yaml +7 -0
- package/package.json +35 -0
- package/starter/noxt.config.yaml +9 -0
- package/starter/package.json +14 -0
- package/starter/public/css/main.css +31 -0
- package/starter/public/img/favicon.png +0 -0
- package/starter/public/js/main.js +1 -0
- package/starter/views/layouts/Layout.jsx +52 -0
- package/starter/views/pages/PageIndex.jsx +25 -0
- package/starter/views/pages/PageTask.jsx +46 -0
- package/starter/views/pages/PageTasks.jsx +48 -0
- package/starter/views/templates/AlertButton.jsx +13 -0
- package/starter/views/templates/Header.jsx +7 -0
- package/starter/views/templates/Sidebar.jsx +10 -0
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
|
+
}
|
package/bin/cmd_start.js
ADDED
|
@@ -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
|
+
}
|
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,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
|
+
}
|