@tishlang/create-tish-app 1.13.0 → 1.13.2
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 +45 -0
- package/index.js +72 -51
- package/package.json +1 -1
- package/templates/docs/build.tish +27 -0
- package/templates/docs/content/index.md +3 -0
- package/templates/docs/dev-server.tish +42 -0
- package/templates/docs/package.json +10 -0
- package/templates/http-hello/package.json +10 -0
- package/templates/http-hello/src/main.tish +14 -0
- package/templates/lattish/dev-server.tish +42 -0
- package/templates/lattish/package.json +13 -0
- package/templates/lattish/public/index.html +13 -0
- package/templates/lattish/src/App.tish +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @tishlang/create-tish-app
|
|
2
|
+
|
|
3
|
+
The official scaffolding tool for Tish projects. It provides templates to quickly bootstrap new applications with zero configuration.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
You can create a new project by running:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @tishlang/create-tish-app [template] [project-name]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If you run it without arguments, it will launch an interactive prompt to help you choose a template and name your project:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @tishlang/create-tish-app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Available Templates
|
|
20
|
+
|
|
21
|
+
- **`lattish`**: A client-side SPA with `@tishlang/lattish` and a Tish static file server.
|
|
22
|
+
- **`http-hello`**: A simple standalone HTTP server written in Tish.
|
|
23
|
+
- **`docs`**: A static site generator for Markdown documentation.
|
|
24
|
+
|
|
25
|
+
## Local Development & Debugging
|
|
26
|
+
|
|
27
|
+
If you are modifying the scaffolding tool or adding new templates in the `templates/` directory, you can easily test it locally.
|
|
28
|
+
|
|
29
|
+
### Method 1: Direct Execution
|
|
30
|
+
Run the `index.js` script directly using Node.js in a scratch directory:
|
|
31
|
+
```bash
|
|
32
|
+
mkdir -p /tmp/test-app && cd /tmp/test-app
|
|
33
|
+
node ~/Projects/tish/tish/npm/create-tish-app/index.js
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Method 2: Using `npm link`
|
|
37
|
+
You can link the package globally to test the CLI command exactly as an end-user would:
|
|
38
|
+
```bash
|
|
39
|
+
# From this directory:
|
|
40
|
+
npm link
|
|
41
|
+
|
|
42
|
+
# Now you can use it anywhere on your machine
|
|
43
|
+
cd /tmp
|
|
44
|
+
create-tish-app docs my-test-docs
|
|
45
|
+
```
|
package/index.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const readline = require('readline');
|
|
6
7
|
|
|
7
8
|
function prompt(question) {
|
|
8
|
-
const readline = require('readline');
|
|
9
9
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10
10
|
return new Promise(resolve => {
|
|
11
11
|
rl.question(question, answer => {
|
|
@@ -15,14 +15,63 @@ function prompt(question) {
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function copyDirSync(src, dest, safeName) {
|
|
19
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
20
|
+
let entries = fs.readdirSync(src, { withFileTypes: true });
|
|
21
|
+
|
|
22
|
+
for (let entry of entries) {
|
|
23
|
+
let srcPath = path.join(src, entry.name);
|
|
24
|
+
let destPath = path.join(dest, entry.name);
|
|
25
|
+
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
copyDirSync(srcPath, destPath, safeName);
|
|
28
|
+
} else {
|
|
29
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
30
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, safeName);
|
|
31
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
18
36
|
async function main() {
|
|
19
|
-
|
|
37
|
+
const templatesDir = path.join(__dirname, 'templates');
|
|
38
|
+
const TEMPLATES = fs.existsSync(templatesDir) ? fs.readdirSync(templatesDir).filter(f => fs.statSync(path.join(templatesDir, f)).isDirectory()) : [];
|
|
39
|
+
|
|
40
|
+
let template = (process.argv[2] && String(process.argv[2]).trim()) || '';
|
|
41
|
+
let name = (process.argv[3] && String(process.argv[3]).trim()) || '';
|
|
42
|
+
|
|
43
|
+
// Shift arguments if the first arg is not a template (allow `create-tish-app my-app` but prompt for template)
|
|
44
|
+
if (template && !TEMPLATES.includes(template)) {
|
|
45
|
+
if (!name) {
|
|
46
|
+
name = template;
|
|
47
|
+
template = '';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!template) {
|
|
52
|
+
console.log('Available templates:');
|
|
53
|
+
TEMPLATES.forEach((t, i) => console.log(` ${i + 1}) ${t}`));
|
|
54
|
+
let selection = await prompt('Select a template (1-' + TEMPLATES.length + '): ');
|
|
55
|
+
const index = parseInt(selection, 10) - 1;
|
|
56
|
+
if (index >= 0 && index < TEMPLATES.length) {
|
|
57
|
+
template = TEMPLATES[index];
|
|
58
|
+
} else if (TEMPLATES.includes(selection)) {
|
|
59
|
+
template = selection;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!TEMPLATES.includes(template)) {
|
|
63
|
+
console.error('Invalid template selection.');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
20
68
|
if (!name) {
|
|
21
69
|
name = await prompt('Project name: ');
|
|
22
70
|
name = name.trim();
|
|
23
71
|
}
|
|
72
|
+
|
|
24
73
|
if (!name) {
|
|
25
|
-
console.error('Please provide a project name
|
|
74
|
+
console.error('Please provide a project name.');
|
|
26
75
|
process.exit(1);
|
|
27
76
|
}
|
|
28
77
|
|
|
@@ -30,6 +79,7 @@ async function main() {
|
|
|
30
79
|
console.error('Project name must be a single folder name, not a path.');
|
|
31
80
|
process.exit(1);
|
|
32
81
|
}
|
|
82
|
+
|
|
33
83
|
if (name === '.' || name === '..') {
|
|
34
84
|
console.error('Invalid project name.');
|
|
35
85
|
process.exit(1);
|
|
@@ -41,57 +91,28 @@ async function main() {
|
|
|
41
91
|
process.exit(1);
|
|
42
92
|
}
|
|
43
93
|
|
|
44
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
45
|
-
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
|
46
|
-
|
|
47
94
|
const safeName = name.replace(/[^a-z0-9-]/gi, '-').replace(/-+/g, '-').toLowerCase() || 'tish-app';
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.
|
|
52
|
-
|
|
53
|
-
'zectre.yaml': `name: ${safeName}
|
|
54
|
-
`,
|
|
55
|
-
'package.json': JSON.stringify({
|
|
56
|
-
name: safeName,
|
|
57
|
-
version: '0.1.0',
|
|
58
|
-
private: true,
|
|
59
|
-
tish: { source: './src/main.tish' },
|
|
60
|
-
}, null, 2),
|
|
61
|
-
'.gitignore': `# Build output
|
|
62
|
-
/tish_out
|
|
63
|
-
*.exe
|
|
64
|
-
`,
|
|
65
|
-
'README.md': `# ${name}
|
|
66
|
-
|
|
67
|
-
A [Tish](https://github.com/tishlang/tish) project.
|
|
68
|
-
|
|
69
|
-
## Run (interpret)
|
|
70
|
-
|
|
71
|
-
\`\`\`bash
|
|
72
|
-
npx @tishlang/tish run src/main.tish
|
|
73
|
-
# or after installing: tish run src/main.tish
|
|
74
|
-
\`\`\`
|
|
75
|
-
|
|
76
|
-
## Build to native
|
|
77
|
-
|
|
78
|
-
\`\`\`bash
|
|
79
|
-
npx @tishlang/tish build src/main.tish -o app
|
|
80
|
-
./app
|
|
81
|
-
\`\`\`
|
|
82
|
-
`,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
for (const [file, content] of Object.entries(files)) {
|
|
86
|
-
fs.writeFileSync(path.join(dir, file), content, 'utf8');
|
|
95
|
+
|
|
96
|
+
const tplDir = path.join(templatesDir, template);
|
|
97
|
+
if (!fs.existsSync(tplDir)) {
|
|
98
|
+
console.error('Template not found: ' + template);
|
|
99
|
+
process.exit(1);
|
|
87
100
|
}
|
|
88
101
|
|
|
89
|
-
console.log(`
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
console.log(
|
|
93
|
-
console.log('
|
|
94
|
-
console.log('
|
|
102
|
+
console.log(`Copying files from ${template} template...`);
|
|
103
|
+
copyDirSync(tplDir, dir, safeName);
|
|
104
|
+
|
|
105
|
+
console.log(`\nSuccess! Created ${name} at ${dir}`);
|
|
106
|
+
console.log('Inside that directory, you can run several commands:');
|
|
107
|
+
console.log('\n npm run dev');
|
|
108
|
+
console.log(' Starts the development server.');
|
|
109
|
+
console.log('\n npm run build');
|
|
110
|
+
console.log(' Builds the app for production.');
|
|
111
|
+
console.log('\n npm run start');
|
|
112
|
+
console.log(' Runs the built app in production mode.\n');
|
|
113
|
+
console.log('We suggest that you begin by typing:');
|
|
114
|
+
console.log(`\n cd ${name}`);
|
|
115
|
+
console.log(' npm run dev\n');
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
main().catch(err => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { fileExists, readFile, writeFile, mkdir } from "fs"
|
|
2
|
+
|
|
3
|
+
fn buildDocs() {
|
|
4
|
+
let content = ""
|
|
5
|
+
if (fileExists("content/index.md")) {
|
|
6
|
+
content = readFile("content/index.md")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let html = `<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<title>Docs</title>
|
|
14
|
+
<style>body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; }</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div class="content">${content}</div>
|
|
18
|
+
<p><em>(In a full implementation, you would convert Markdown to HTML here)</em></p>
|
|
19
|
+
</body>
|
|
20
|
+
</html>`
|
|
21
|
+
|
|
22
|
+
mkdir("public")
|
|
23
|
+
writeFile("public/index.html", html)
|
|
24
|
+
console.log("Built docs to public/index.html")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
buildDocs()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { fileExists, isDir } from "fs"
|
|
2
|
+
import { serve } from "http"
|
|
3
|
+
import { process } from "process"
|
|
4
|
+
|
|
5
|
+
fn contentType(path) {
|
|
6
|
+
if (path.endsWith(".html")) { return "text/html; charset=utf-8" }
|
|
7
|
+
if (path.endsWith(".css")) { return "text/css; charset=utf-8" }
|
|
8
|
+
if (path.endsWith(".js") || path.endsWith(".mjs")) { return "application/javascript; charset=utf-8" }
|
|
9
|
+
return "application/octet-stream"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fn serveFile(path) {
|
|
13
|
+
let ct = contentType(path)
|
|
14
|
+
return { status: 200, headers: { "Content-Type": ct, "Cache-Control": "no-store" }, file: path }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn handleRequest(req) {
|
|
18
|
+
let p = req.path
|
|
19
|
+
if (p === "/" || p === "") { p = "/index.html" }
|
|
20
|
+
if (p.charAt(0) === "/") { p = p.substring(1) }
|
|
21
|
+
|
|
22
|
+
let candidates = ["public/" + p, p]
|
|
23
|
+
let i = 0
|
|
24
|
+
while (i < candidates.length) {
|
|
25
|
+
let path = candidates[i]
|
|
26
|
+
if (fileExists(path) && !isDir(path)) { return serveFile(path) }
|
|
27
|
+
i = i + 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (fileExists("public/index.html") && req.path.indexOf(".") < 0) {
|
|
31
|
+
return serveFile("public/index.html")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { status: 404, body: "Not found: " + req.path }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let port = 8080
|
|
38
|
+
if (process !== null && "env" in process && process.env !== null && "PORT" in process.env) {
|
|
39
|
+
port = parseInt(process.env["PORT"])
|
|
40
|
+
}
|
|
41
|
+
console.log("Dev server running at http://127.0.0.1:" + port)
|
|
42
|
+
serve(port, handleRequest)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "tish run --feature fs build.tish && tish run --feature http,fs,process dev-server.tish",
|
|
7
|
+
"build": "tish run --feature fs build.tish",
|
|
8
|
+
"start": "tish run --feature http,fs,process dev-server.tish"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
fn handleRequest(req) {
|
|
2
|
+
console.log("Request:", req.method, req.path)
|
|
3
|
+
if (req.path === "/health") { return { status: 200, body: "OK" } }
|
|
4
|
+
|
|
5
|
+
return {
|
|
6
|
+
status: 200,
|
|
7
|
+
headers: { contentType: "application/json" },
|
|
8
|
+
body: JSON.stringify({ message: "Hello from Tish HTTP Server!" })
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let port = 8080
|
|
13
|
+
console.log(`Starting server on port ${port}...`)
|
|
14
|
+
serve(port, handleRequest)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { fileExists, isDir } from "fs"
|
|
2
|
+
import { serve } from "http"
|
|
3
|
+
import { process } from "process"
|
|
4
|
+
|
|
5
|
+
fn contentType(path) {
|
|
6
|
+
if (path.endsWith(".html")) { return "text/html; charset=utf-8" }
|
|
7
|
+
if (path.endsWith(".css")) { return "text/css; charset=utf-8" }
|
|
8
|
+
if (path.endsWith(".js") || path.endsWith(".mjs")) { return "application/javascript; charset=utf-8" }
|
|
9
|
+
return "application/octet-stream"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fn serveFile(path) {
|
|
13
|
+
let ct = contentType(path)
|
|
14
|
+
return { status: 200, headers: { "Content-Type": ct, "Cache-Control": "no-store" }, file: path }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn handleRequest(req) {
|
|
18
|
+
let p = req.path
|
|
19
|
+
if (p === "/" || p === "") { p = "/index.html" }
|
|
20
|
+
if (p.charAt(0) === "/") { p = p.substring(1) }
|
|
21
|
+
|
|
22
|
+
let candidates = ["public/" + p, p]
|
|
23
|
+
let i = 0
|
|
24
|
+
while (i < candidates.length) {
|
|
25
|
+
let path = candidates[i]
|
|
26
|
+
if (fileExists(path) && !isDir(path)) { return serveFile(path) }
|
|
27
|
+
i = i + 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (fileExists("public/index.html") && req.path.indexOf(".") < 0) {
|
|
31
|
+
return serveFile("public/index.html")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { status: 404, body: "Not found: " + req.path }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let port = 8080
|
|
38
|
+
if (process !== null && "env" in process && process.env !== null && "PORT" in process.env) {
|
|
39
|
+
port = parseInt(process.env["PORT"])
|
|
40
|
+
}
|
|
41
|
+
console.log("Dev server running at http://127.0.0.1:" + port)
|
|
42
|
+
serve(port, handleRequest)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "tish run --feature http,fs,process dev-server.tish",
|
|
7
|
+
"build": "tish build --target js src/App.tish -o public/dist/App",
|
|
8
|
+
"start": "tish run --feature http,fs,process dev-server.tish"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@tishlang/lattish": "latest"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{PROJECT_NAME}}</title>
|
|
7
|
+
<style>body { font-family: system-ui, sans-serif; padding: 2rem; }</style>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/dist/App.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useState, createRoot } from 'lattish'
|
|
2
|
+
|
|
3
|
+
fn App() {
|
|
4
|
+
let [count, setCount] = useState(0)
|
|
5
|
+
return <div class="container">
|
|
6
|
+
<h1>Hello, Tish + Lattish!</h1>
|
|
7
|
+
<p>Count: {count}</p>
|
|
8
|
+
<button onclick={() => setCount(count + 1)}>Increment</button>
|
|
9
|
+
</div>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
createRoot(document.getElementById("root")).render(App)
|