create-nodebuilderjs 0.0.0-stage → 0.1.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/index.js ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ // bun create nodebuilderjs [dir] [--title "Meu App"] [--no-install] [--no-git]
3
+ // Plain JS (not TS) so it runs under both `bun create` and `npm create`.
4
+ import { spawnSync } from "node:child_process";
5
+ import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { basename, dirname, join, relative, resolve } from "node:path";
7
+ import { createInterface } from "node:readline/promises";
8
+ import { fileURLToPath } from "node:url";
9
+ import { parseArgs } from "node:util";
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const { version } = JSON.parse(readFileSync(join(here, "package.json"), "utf8"));
13
+
14
+ const { values: opts, positionals } = parseArgs({
15
+ allowPositionals: true,
16
+ options: {
17
+ title: { type: "string" },
18
+ "no-install": { type: "boolean" },
19
+ "no-git": { type: "boolean" },
20
+ help: { type: "boolean", short: "h" },
21
+ },
22
+ });
23
+
24
+ if (opts.help) {
25
+ console.log(`uso: bun create nodebuilderjs [pasta] [--title "Meu App"] [--no-install] [--no-git]`);
26
+ process.exit(0);
27
+ }
28
+
29
+ let dir = positionals[0];
30
+ if (!dir) {
31
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
32
+ dir = (await rl.question("Nome do projeto: (meu-app) ")).trim() || "meu-app";
33
+ rl.close();
34
+ }
35
+ const target = resolve(dir);
36
+ if (existsSync(target) && readdirSync(target).length) {
37
+ console.error(`A pasta ${dir} já existe e não está vazia.`);
38
+ process.exit(1);
39
+ }
40
+
41
+ const name = basename(target).toLowerCase().replace(/[^a-z0-9._-]/g, "-");
42
+ const title = opts.title ?? basename(target).replace(/[-_.]+/g, " ").replace(/^\w/, (c) => c.toUpperCase());
43
+ const appId = `dev.nodebuilder.${name.replace(/[^a-z0-9]/g, "_")}`;
44
+
45
+ cpSync(join(here, "template"), target, { recursive: true });
46
+ // npm drops .gitignore from published packages, so the template ships it as _gitignore.
47
+ renameSync(join(target, "_gitignore"), join(target, ".gitignore"));
48
+
49
+ const fill = (file) => {
50
+ const path = join(target, file);
51
+ writeFileSync(path, readFileSync(path, "utf8").replaceAll("{{title}}", title).replaceAll("{{appId}}", appId));
52
+ };
53
+ ["src/App.tsx", "README.md"].forEach(fill);
54
+
55
+ const pkg = {
56
+ name,
57
+ version: "0.1.0",
58
+ private: true,
59
+ type: "module",
60
+ scripts: {
61
+ start: "bun src/main.tsx",
62
+ dev: "bun --watch src/main.tsx",
63
+ test: "bun test",
64
+ typecheck: "tsc --noEmit",
65
+ package: `nodebuilder package src/main.tsx --title "${title}" --app-id ${appId}`,
66
+ },
67
+ dependencies: { "@nodebuilderjs/core": `^${version}` },
68
+ devDependencies: { "@types/bun": "^1.4.2", typescript: "7.0.2" },
69
+ };
70
+ writeFileSync(join(target, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
71
+
72
+ const run = (cmd, args) => spawnSync(cmd, args, { cwd: target, stdio: "inherit" }).status === 0;
73
+ const hasBun = spawnSync("bun", ["--version"], { stdio: "ignore" }).status === 0;
74
+
75
+ if (!opts["no-install"]) {
76
+ if (!hasBun) console.warn("Bun não encontrado: instale em https://bun.sh e rode `bun install`.");
77
+ else if (!run("bun", ["install"])) console.warn("`bun install` falhou; rode de novo dentro da pasta.");
78
+ }
79
+ if (!opts["no-git"] && spawnSync("git", ["--version"], { stdio: "ignore" }).status === 0) {
80
+ run("git", ["init", "-q"]);
81
+ }
82
+
83
+ const cd = relative(process.cwd(), target) || ".";
84
+ console.log(`
85
+ Pronto! ${title} criado em ${cd}
86
+
87
+ cd ${cd}
88
+ bun run dev # abre a janela e recarrega ao salvar
89
+ bun test # testes sem janela
90
+ bun run package # AppImage, .deb, .rpm e .tar.gz em dist/
91
+ `);
package/package.json CHANGED
@@ -1,6 +1,27 @@
1
1
  {
2
2
  "name": "create-nodebuilderjs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Cria um app nodebuilder: bun create nodebuilderjs meu-app",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/felipebrgs1/nodebuilderjs.git",
10
+ "directory": "packages/create-nodebuilderjs"
11
+ },
12
+ "homepage": "https://github.com/felipebrgs1/nodebuilderjs#readme",
13
+ "bugs": "https://github.com/felipebrgs1/nodebuilderjs/issues",
14
+ "bin": {
15
+ "create-nodebuilderjs": "./index.js"
16
+ },
17
+ "files": [
18
+ "index.js",
19
+ "template"
20
+ ],
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ }
27
+ }
@@ -0,0 +1,29 @@
1
+ # AGENTS.md
2
+
3
+ App nodebuilder: UI nativa para Linux em TSX, **sem DOM e sem React**. Leia antes de mudar código.
4
+
5
+ ## Comandos
6
+ - `bun run dev` abre o app; `bun test` roda os testes sem janela; `bun run typecheck` checa tipos.
7
+ - Antes de concluir: `bun run typecheck && bun test`.
8
+
9
+ ## Modelo (igual ao SolidJS, não ao React)
10
+ - **Componentes rodam uma vez.** Não há re-render nem hooks. Estado é `createSignal`;
11
+ derivados são `createMemo`; efeitos são `createEffect` (+ `onCleanup`).
12
+ - **Só funções são reativas.** `<Text>{count}</Text>` ou `text={() => ...}` atualizam;
13
+ `<Text>{count()}</Text>` é lido uma vez e nunca muda. O mesmo vale para estilo:
14
+ `background={() => (ativo() ? "#3584e4" : "#0000")}`.
15
+ - Listas: `<For each={lista}>{(item) => ...}</For>` (chave por referência). Condicional:
16
+ `<Show when={() => ...} fallback={...}>`.
17
+
18
+ ## Widgets e estilo
19
+ - `Window` (raiz, uma por app), `Column`/`Row`/`Box`, `Scroll`, `Text`, `Button`, `TextField`,
20
+ `Checkbox`, `Card`, `Divider`, `Spacer`. Tudo vem de `@nodebuilderjs/core`.
21
+ - Estilo são props planas, não CSS: `padding`, `gap`, `grow`, `align`, `justify`, `width`,
22
+ `height`, `background`, `radius`, `borderWidth`, `borderColor`, `color`, `fontSize`, `bold`.
23
+ Layout é flexbox; `align` padrão é `stretch`. `color`, `fontSize` e `bold` são herdados.
24
+ - Cores padrão em `theme` (`theme.text.secondary`, `theme.accent.base`...).
25
+ - Não existem `className`, `style={{}}`, `div`, `span`, `onChange` de DOM nem `useState`.
26
+
27
+ ## Testes
28
+ - `@nodebuilderjs/core/testing`: `mount(() => <App />)`, `findByText(root, "texto")`,
29
+ `click(node)` (sobe até o pai clicável). Métricas de texto falsas: 8 px por caractere.
@@ -0,0 +1,14 @@
1
+ # {{title}}
2
+
3
+ App desktop nativo para Linux feito com [nodebuilder](https://www.npmjs.com/package/@nodebuilderjs/core):
4
+ componentes TSX com reatividade estilo SolidJS, desenhados por um núcleo nativo (Wayland/X11 + GLES).
5
+
6
+ ```sh
7
+ bun run dev # abre a janela e reinicia ao salvar
8
+ bun start # roda uma vez
9
+ bun test # testes sem janela (test/)
10
+ bun run typecheck # TypeScript 7
11
+ bun run package # AppImage, .deb, .rpm e .tar.gz em dist/
12
+ ```
13
+
14
+ Requisitos: Linux com Wayland ou X11, [Bun](https://bun.sh) 1.4+.
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ dist/
@@ -0,0 +1,24 @@
1
+ import { Button, Column, Row, Text, Window, createSignal, theme } from "@nodebuilderjs/core";
2
+
3
+ export function App() {
4
+ const [count, setCount] = createSignal(0);
5
+ const times = () => `Você clicou ${count()} ${count() === 1 ? "vez" : "vezes"}`;
6
+
7
+ return (
8
+ <Window title="{{title}}" appId="{{appId}}" width={480} height={360}>
9
+ <Column grow={1} align="center" justify="center" gap={16} padding={24}>
10
+ <Text fontSize={24} bold>
11
+ {"{{title}}"}
12
+ </Text>
13
+ <Text color={theme.text.secondary}>{times}</Text>
14
+ <Row gap={8}>
15
+ <Button label="Zerar" variant="flat" onClick={() => setCount(0)} />
16
+ <Button label="Clique aqui" variant="primary" onClick={() => setCount((c) => c + 1)} />
17
+ </Row>
18
+ <Text fontSize={12} color={theme.text.secondary}>
19
+ Edite src/App.tsx e salve: o bun run dev reabre a janela.
20
+ </Text>
21
+ </Column>
22
+ </Window>
23
+ );
24
+ }
@@ -0,0 +1,4 @@
1
+ import { run } from "@nodebuilderjs/core";
2
+ import { App } from "./App.js";
3
+
4
+ run(() => <App />);
@@ -0,0 +1,14 @@
1
+ import { expect, test } from "bun:test";
2
+ import { click, findByText, mount } from "@nodebuilderjs/core/testing";
3
+ import { App } from "../src/App.js";
4
+
5
+ test("o botão conta os cliques", () => {
6
+ const { root } = mount(() => <App />);
7
+ expect(findByText(root, "Você clicou 0 vezes")).toBeDefined();
8
+
9
+ click(findByText(root, "Clique aqui")!);
10
+ expect(findByText(root, "Você clicou 1 vez")).toBeDefined();
11
+
12
+ click(findByText(root, "Zerar")!);
13
+ expect(findByText(root, "Você clicou 0 vezes")).toBeDefined();
14
+ });
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "Preserve",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "jsx": "react-jsx",
9
+ "jsxImportSource": "@nodebuilderjs/core",
10
+ "skipLibCheck": true,
11
+ "types": ["bun"]
12
+ },
13
+ "include": ["src", "test"]
14
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.