@tishlang/create-tish-app 1.13.0 → 2.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/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
- let name = (process.argv[2] && String(process.argv[2]).trim()) || '';
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 (argument or when prompted). Example: npx @tishlang/create-tish-app my-app');
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
- const files = {
49
- 'src/main.tish': `// ${name} - Tish app
50
- let message = "Hello, Tish!"
51
- console.log(message)
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(`Created ${name} at ${dir}`);
90
- console.log('');
91
- console.log('Next steps:');
92
- console.log(` cd ${name}`);
93
- console.log(' npx @tishlang/tish run src/main.tish');
94
- console.log(' # or: npx @tishlang/tish build src/main.tish -o app && ./app');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tishlang/create-tish-app",
3
- "version": "1.13.0",
3
+ "version": "2.0.0",
4
4
  "description": "Scaffold a new Tish project",
5
5
  "license": "PIF",
6
6
  "repository": {
@@ -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,3 @@
1
+ # Welcome to {{PROJECT_NAME}}
2
+
3
+ This is a fully prerendered static site using Tish.
@@ -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,10 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "tish run --feature http src/main.tish",
7
+ "build": "tish build src/main.tish -o app",
8
+ "start": "./app"
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)