create-table-app 0.0.1
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 +29 -0
- package/dist/index.mjs +176 -0
- package/package.json +47 -0
- package/templates/default/index.html +12 -0
- package/templates/default/package.json +28 -0
- package/templates/default/src/app/movie/[id]/page.tsx +29 -0
- package/templates/default/src/app/page.tsx +37 -0
- package/templates/default/src/main.tsx +21 -0
- package/templates/default/src/styles/globals.css +35 -0
- package/templates/default/tsconfig.json +13 -0
- package/templates/default/vite.config.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# create-table-app
|
|
2
|
+
|
|
3
|
+
Create a new Table.js Smart TV application in one command.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-table-app my-tv-app
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with your preferred package manager:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm create table-app my-tv-app
|
|
15
|
+
yarn create table-app my-tv-app
|
|
16
|
+
bun create table-app my-tv-app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## What's Included
|
|
20
|
+
|
|
21
|
+
- File-based routing
|
|
22
|
+
- Focus management
|
|
23
|
+
- Tailwind CSS v4
|
|
24
|
+
- TypeScript
|
|
25
|
+
- Vite
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
MIT
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import path2 from "path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
|
|
9
|
+
// src/prompts.ts
|
|
10
|
+
import prompts from "prompts";
|
|
11
|
+
async function askProjectOptions(nameArg) {
|
|
12
|
+
const answers = await prompts([
|
|
13
|
+
{
|
|
14
|
+
type: nameArg ? null : "text",
|
|
15
|
+
name: "name",
|
|
16
|
+
message: "Project name",
|
|
17
|
+
initial: "my-table-app",
|
|
18
|
+
validate: (v) => {
|
|
19
|
+
return /^[a-z0-9-_]+$/.test(v) || "Use lowercase letters, numbers, - or _";
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: "select",
|
|
24
|
+
name: "platform",
|
|
25
|
+
message: "Target platform",
|
|
26
|
+
choices: [
|
|
27
|
+
{ title: "All platforms", value: "all" },
|
|
28
|
+
{ title: "Tizen (Samsung)", value: "tizen" },
|
|
29
|
+
{ title: "WebOS (LG)", value: "webos" },
|
|
30
|
+
{ title: "Android TV", value: "android" }
|
|
31
|
+
],
|
|
32
|
+
initial: 0
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: "select",
|
|
36
|
+
name: "packageManager",
|
|
37
|
+
message: "Package manager",
|
|
38
|
+
choices: [
|
|
39
|
+
{ title: "pnpm", value: "pnpm" },
|
|
40
|
+
{ title: "npm", value: "npm" },
|
|
41
|
+
{ title: "yarn", value: "yarn" },
|
|
42
|
+
{ title: "bun", value: "bun" }
|
|
43
|
+
],
|
|
44
|
+
initial: 0
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "confirm",
|
|
48
|
+
name: "installDeps",
|
|
49
|
+
message: "Install dependencies?",
|
|
50
|
+
initial: true
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: "confirm",
|
|
54
|
+
name: "initGit",
|
|
55
|
+
message: "Initialize git repository?",
|
|
56
|
+
initial: true
|
|
57
|
+
}
|
|
58
|
+
], {
|
|
59
|
+
onCancel: () => {
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
name: nameArg ?? answers.name,
|
|
65
|
+
platform: answers.platform,
|
|
66
|
+
packageManager: answers.packageManager,
|
|
67
|
+
installDeps: answers.installDeps,
|
|
68
|
+
initGit: answers.initGit
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/generator.ts
|
|
73
|
+
import path from "path";
|
|
74
|
+
import fs from "fs-extra";
|
|
75
|
+
import { execa } from "execa";
|
|
76
|
+
var TEMPLATE_DIR = path.join(
|
|
77
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
78
|
+
"..",
|
|
79
|
+
"templates",
|
|
80
|
+
"default"
|
|
81
|
+
);
|
|
82
|
+
async function applyTemplateVars(filePath, values) {
|
|
83
|
+
const TEXT_EXTENSIONS = [".ts", ".tsx", ".js", ".json", ".html", ".css", ".md"];
|
|
84
|
+
const ext = path.extname(filePath);
|
|
85
|
+
if (!TEXT_EXTENSIONS.includes(ext)) return;
|
|
86
|
+
let content = await fs.readFile(filePath, "utf-8");
|
|
87
|
+
let changed = false;
|
|
88
|
+
for (const [key, value] of Object.entries(values)) {
|
|
89
|
+
if (content.includes(key)) {
|
|
90
|
+
content = content.replaceAll(key, value);
|
|
91
|
+
changed = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (changed) {
|
|
95
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function walk(dir, values) {
|
|
99
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
100
|
+
await Promise.all(
|
|
101
|
+
entries.map(async (entry) => {
|
|
102
|
+
const fullPath = path.join(dir, entry.name);
|
|
103
|
+
if (entry.isDirectory()) {
|
|
104
|
+
await walk(fullPath, values);
|
|
105
|
+
} else {
|
|
106
|
+
await applyTemplateVars(fullPath, values);
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
async function generateProject(targetDir, options) {
|
|
112
|
+
await fs.copy(TEMPLATE_DIR, targetDir);
|
|
113
|
+
const values = {
|
|
114
|
+
"{{name}}": options.name,
|
|
115
|
+
"{{platform}}": options.platform
|
|
116
|
+
};
|
|
117
|
+
await walk(targetDir, values);
|
|
118
|
+
}
|
|
119
|
+
async function installDeps(dir, pm) {
|
|
120
|
+
await execa(pm, ["install"], { cwd: dir, stdio: "inherit" });
|
|
121
|
+
}
|
|
122
|
+
async function initGit(dir) {
|
|
123
|
+
await execa("git", ["init"], { cwd: dir });
|
|
124
|
+
await execa("git", ["add", "-A"], { cwd: dir });
|
|
125
|
+
await execa("git", ["commit", "-m", "chore: initial commit from create-table-app"], { cwd: dir });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/index.ts
|
|
129
|
+
var program = new Command();
|
|
130
|
+
program.name("create-table-app").description("Create a new Table.js Smart TV application").version("0.0.1").argument("[project-name]", "Name of the project").action(async (projectName) => {
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(chalk.bold(" table.js") + chalk.dim(" \u2014 Smart TV framework"));
|
|
133
|
+
console.log();
|
|
134
|
+
const options = await askProjectOptions(projectName);
|
|
135
|
+
const targetDir = path2.resolve(process.cwd(), options.name);
|
|
136
|
+
console.log();
|
|
137
|
+
const spinner = ora("Creating project structure").start();
|
|
138
|
+
try {
|
|
139
|
+
await generateProject(targetDir, options);
|
|
140
|
+
spinner.succeed("Project structure created");
|
|
141
|
+
} catch (err) {
|
|
142
|
+
spinner.fail("Failed to create project");
|
|
143
|
+
console.error(err);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
if (options.installDeps) {
|
|
147
|
+
const depSpinner = ora("Installing dependencies").start();
|
|
148
|
+
try {
|
|
149
|
+
await installDeps(targetDir, options.packageManager);
|
|
150
|
+
depSpinner.succeed("Dependencies installed");
|
|
151
|
+
} catch {
|
|
152
|
+
depSpinner.fail("Failed to install dependencies");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (options.initGit) {
|
|
156
|
+
const gitSpinner = ora("Initializing git").start();
|
|
157
|
+
try {
|
|
158
|
+
await initGit(targetDir);
|
|
159
|
+
gitSpinner.succeed("Git initialized");
|
|
160
|
+
} catch {
|
|
161
|
+
gitSpinner.fail("Failed to initialize git");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
console.log();
|
|
165
|
+
console.log(chalk.green(" Project ready!"));
|
|
166
|
+
console.log();
|
|
167
|
+
console.log(" Next steps:");
|
|
168
|
+
console.log();
|
|
169
|
+
console.log(chalk.dim(` cd ${options.name}`));
|
|
170
|
+
if (!options.installDeps) {
|
|
171
|
+
console.log(chalk.dim(` ${options.packageManager} install`));
|
|
172
|
+
}
|
|
173
|
+
console.log(chalk.dim(` ${options.packageManager} run dev`));
|
|
174
|
+
console.log();
|
|
175
|
+
});
|
|
176
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-table-app",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Create a new Table.js Smart TV app in one command",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-table-app": "./dist/index.mjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"templates"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"clean": "rm -rf dist"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"smart-tv",
|
|
20
|
+
"create-app",
|
|
21
|
+
"cli",
|
|
22
|
+
"tizen",
|
|
23
|
+
"webos",
|
|
24
|
+
"android-tv"
|
|
25
|
+
],
|
|
26
|
+
"author": "Table.js Team",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/tablejs/tablejs"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"chalk": "^5.6.2",
|
|
34
|
+
"commander": "^14.0.3",
|
|
35
|
+
"execa": "^9.6.1",
|
|
36
|
+
"fs-extra": "^11.3.4",
|
|
37
|
+
"ora": "^9.3.0",
|
|
38
|
+
"prompts": "^2.4.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@table/tsconfig": "workspace:*",
|
|
42
|
+
"@types/fs-extra": "^11.0.4",
|
|
43
|
+
"@types/prompts": "^2.4.9",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^6.0.2"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=1920">
|
|
6
|
+
<title>{{name}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "table build --platform {{platform}}",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@table-js/core": "latest",
|
|
14
|
+
"@table-js/ui": "latest",
|
|
15
|
+
"react": "^19.2.4",
|
|
16
|
+
"react-dom": "^19.2.4"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@table-js/tsconfig": "latest",
|
|
20
|
+
"@types/react": "^19.2.4",
|
|
21
|
+
"@types/react-dom": "^19.2.4",
|
|
22
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
23
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
24
|
+
"tailwindcss": "^4.2.2",
|
|
25
|
+
"typescript": "^6.0.2",
|
|
26
|
+
"vite": "^8.0.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Focusable, useParams, useRouter } from '@table/core'
|
|
2
|
+
|
|
3
|
+
interface MovieParams {
|
|
4
|
+
id: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default function MoviePage() {
|
|
8
|
+
const { id } = useParams<MovieParams>()
|
|
9
|
+
const { back } = useRouter()
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="min-h-screen bg-(--color-background) text-(--color-text-primary) p-16">
|
|
13
|
+
<Focusable onSelect={back} autoFocus>
|
|
14
|
+
{(focused) => (
|
|
15
|
+
<button
|
|
16
|
+
className={`
|
|
17
|
+
px-6 py-3 rounded-(--radius-button) bg-(--color-surface) mb-8
|
|
18
|
+
transition-colors duration-150
|
|
19
|
+
${focused ? 'bg-(--color-surface-hover) outline-3 outline-(--color-accent)' : ''}
|
|
20
|
+
`}
|
|
21
|
+
>
|
|
22
|
+
← Back
|
|
23
|
+
</button>
|
|
24
|
+
)}
|
|
25
|
+
</Focusable>
|
|
26
|
+
<h1 className="text-5xl font-bold">Movie #{id}</h1>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Focusable, FocusGroup, useRouter } from '@table/core'
|
|
2
|
+
|
|
3
|
+
const movies = [
|
|
4
|
+
{ id: '1', title: 'Inception' },
|
|
5
|
+
{ id: '2', title: 'Interstellar' },
|
|
6
|
+
{ id: '3', title: 'The Dark Knight' },
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
export default function HomePage() {
|
|
10
|
+
const { navigate } = useRouter()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="min-h-screen bg-(--color-background) text-(--color-text-primary) p-16">
|
|
14
|
+
<h1 className="text-4xl font-bold mb-12">My TV App</h1>
|
|
15
|
+
<FocusGroup orientation="horizontal" loop>
|
|
16
|
+
{movies.map((movie) => (
|
|
17
|
+
<Focusable
|
|
18
|
+
key={movie.id}
|
|
19
|
+
onSelect={() => navigate(`/movie/${movie.id}`)}
|
|
20
|
+
>
|
|
21
|
+
{(focused) => (
|
|
22
|
+
<div
|
|
23
|
+
className={`
|
|
24
|
+
w-48 h-72 rounded-(--radius-card) bg-(--color-surface)
|
|
25
|
+
flex items-end p-4 mr-6 transition-transform duration-150
|
|
26
|
+
${focused ? 'scale-105 outline-3 outline-(--color-accent)' : ''}
|
|
27
|
+
`}
|
|
28
|
+
>
|
|
29
|
+
<span className="font-semibold">{movie.title}</span>
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
</Focusable>
|
|
33
|
+
))}
|
|
34
|
+
</FocusGroup>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import { TableApp, defineRoutes } from '@table/core'
|
|
4
|
+
import './styles/globals.css'
|
|
5
|
+
|
|
6
|
+
const routes = defineRoutes([
|
|
7
|
+
{
|
|
8
|
+
path: '/',
|
|
9
|
+
component: () => import('./app/page'),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
path: '/movie/[id]',
|
|
13
|
+
component: () => import('./app/movie/[id]/page'),
|
|
14
|
+
},
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
createRoot(document.getElementById('root')!).render(
|
|
18
|
+
<StrictMode>
|
|
19
|
+
<TableApp routes={routes} />
|
|
20
|
+
</StrictMode>,
|
|
21
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--screen-tv: 1920px;
|
|
5
|
+
|
|
6
|
+
--color-background: #0a0a0a;
|
|
7
|
+
--color-surface: #18181b;
|
|
8
|
+
--color-surface-hover: #27272a;
|
|
9
|
+
--color-border: #3f3f46;
|
|
10
|
+
--color-text-primary: #fafafa;
|
|
11
|
+
--color-text-secondary: #a1a1aa;
|
|
12
|
+
--color-accent: #6366f1;
|
|
13
|
+
--color-accent-hover: #818cf8;
|
|
14
|
+
|
|
15
|
+
--radius-card: 0.75rem;
|
|
16
|
+
--radius-button: 0.5rem;
|
|
17
|
+
|
|
18
|
+
--font-sans: 'Inter', system-ui, sans-serif;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* {
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
background-color: var(--color-background);
|
|
27
|
+
color: var(--color-text-primary);
|
|
28
|
+
font-family: var(--font-sans);
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[data-focused="true"] {
|
|
33
|
+
outline: 3px solid var(--color-accent);
|
|
34
|
+
outline-offset: 3px;
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@table/tsconfig/base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"noEmit": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src", "vite.config.ts"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
react(),
|
|
8
|
+
tailwindcss()
|
|
9
|
+
],
|
|
10
|
+
define: {
|
|
11
|
+
__TABLE_PLATFORM__: JSON.stringify("{{platform}}")
|
|
12
|
+
}
|
|
13
|
+
})
|