@robineb/project-cli 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robineb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # project-cli
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@robineb/project-cli.svg)](https://www.npmjs.com/package/@robineb/project-cli)
4
+
5
+ Persönliches CLI-Tool, das neue Projekte anlegt: Grundgerüst erzeugen, Git
6
+ initialisieren, optional ein Remote-Repository auf GitHub oder Gitea
7
+ anlegen und pushen. Läuft unter Windows und macOS.
8
+
9
+ ## Installation
10
+
11
+ ```
12
+ npm i -g @robineb/project-cli
13
+ ```
14
+
15
+ (Package auf [npmjs.com](https://www.npmjs.com/package/@robineb/project-cli),
16
+ anonym installierbar – kein Account/Token nötig. Scoped, weil der
17
+ naheliegende unscoped Name `project-cli` bereits an ein fremdes Paket
18
+ vergeben ist. Der aufgerufene Befehl heißt trotzdem schlicht `project-cli`,
19
+ das bestimmt der `bin`-Eintrag, nicht der Package-Name. Zusätzlich als
20
+ `@robin1053/project-cli` auf GitHub Packages veröffentlicht, rein für die
21
+ Sichtbarkeit im Repo; installieren lohnt sich darüber nur, wenn man ohnehin
22
+ schon für GitHub Packages authentifiziert ist.)
23
+
24
+ ## Voraussetzungen
25
+
26
+ - Node.js 26+ (das Tool nutzt Node's native, Strip-only TypeScript-Ausführung –
27
+ kein Build-Schritt nötig)
28
+ - Git
29
+ - Für den `esp32`-Stack zusätzlich [PlatformIO Core](https://docs.platformio.org/en/latest/core/installation/index.html) (`pio`)
30
+
31
+ ## Benutzung
32
+
33
+ ```
34
+ node index.ts [name] [--private]
35
+ ```
36
+
37
+ Ohne Argumente führt `node index.ts` interaktiv durch:
38
+
39
+ 1. Projektname
40
+ 2. Stack (`next`, `ts-lib`, `esp32`, …)
41
+ 3. Remote-Repository: keins / GitHub / Gitea
42
+ 4. Grundgerüst wird erzeugt, Git initialisiert und der erste Commit gemacht
43
+ 5. Bei Remote-Wahl: Repo wird angelegt und gepusht
44
+
45
+ `-p, --private` legt ein angelegtes Remote-Repo privat statt öffentlich an.
46
+
47
+ Bricht ab, wenn der Zielordner bereits existiert.
48
+
49
+ ### Stacks
50
+
51
+ | Stack | Beschreibung |
52
+ |---|---|
53
+ | `next` | Next.js (TypeScript, App Router) via `create-next-app` |
54
+ | `ts-lib` | Eigenes TypeScript-Package-Template |
55
+ | `esp32` | PlatformIO/ESP32-Projekt (Arduino-Framework) |
56
+
57
+ Neue Stacks werden einfach als Eintrag in `STACKS` in `index.ts` ergänzt –
58
+ entweder mit `command` (delegiert an einen externen Generator) oder mit
59
+ `template` (kopiert einen Ordner aus `templates/`).
60
+
61
+ ### PlatformIO-Befehle (für `esp32`-Projekte)
62
+
63
+ Im Projektordner eines gescaffoldeten `esp32`-Projekts ausgeführt:
64
+
65
+ ```
66
+ node <pfad-zu-project-cli>/index.ts build [env]
67
+ node <pfad-zu-project-cli>/index.ts upload [env]
68
+ node <pfad-zu-project-cli>/index.ts add-board
69
+ ```
70
+
71
+ - `build` – Firmware bauen (`pio run`)
72
+ - `upload` – Firmware bauen und aufs Board flashen (`pio run -t upload`)
73
+ - `add-board` – neues ESP32-Board suchen (`pio boards espressif32`) und als
74
+ `[env:...]`-Sektion zur `platformio.ini` hinzufügen
75
+
76
+ `env` ist optional – ohne Angabe wird interaktiv aus den in `platformio.ini`
77
+ definierten `[env:...]`-Sektionen ausgewählt.
78
+
79
+ ### Remote-Repos & Zugangsdaten
80
+
81
+ GitHub- bzw. Gitea-Token werden in dieser Reihenfolge aufgelöst:
82
+
83
+ 1. Umgebungsvariable (`GITHUB_TOKEN` bzw. `GITEA_URL`/`GITEA_TOKEN`)
84
+ 2. gespeicherter Wert
85
+ 3. interaktive Abfrage (wird danach für nächstes Mal gespeichert)
86
+
87
+ Tokens landen dabei im OS-eigenen Schlüsselbund über [`@napi-rs/keyring`](https://github.com/napi-rs/keyring-node)
88
+ (Windows Credential Manager, macOS Keychain, Linux Secret Service/libsecret) –
89
+ nicht in einer Klartextdatei. Die Gitea-URL (kein Geheimnis) bleibt plattformgerecht
90
+ in [`conf`](https://github.com/sindresorhus/conf) (`%APPDATA%` unter Windows,
91
+ `~/Library/Application Support` unter macOS, XDG-Pfade unter Linux).
92
+
93
+ Token-Scopes: Gitea braucht `write:repository`. GitHub braucht „Administration"
94
+ (Schreibrecht) auf einem Fine-grained PAT, oder `repo`/`public_repo` auf
95
+ einem Classic PAT.
96
+
97
+ ## Entwicklung
98
+
99
+ Typecheck gegen die Root-`tsconfig.json`:
100
+
101
+ ```
102
+ npx tsc --noEmit
103
+ ```
104
+
105
+ Build für die globale Installation (kompiliert nach `dist/` und kopiert
106
+ `templates/` dorthin, siehe `bin` in `package.json`):
107
+
108
+ ```
109
+ npm run build
110
+ npm i -g .
111
+ ```
112
+
113
+ Es gibt noch keine Tests.
114
+
115
+ ### Veröffentlichen
116
+
117
+ `.github/workflows/publish.yaml` läuft bei jedem GitHub Release (`types:
118
+ [published]`) und veröffentlicht die gebaute Version zweimal parallel:
119
+
120
+ - als `@robineb/project-cli` auf npmjs.com via **Trusted Publishing** (OIDC) –
121
+ kein Secret im Repo, npm tauscht den GitHub-Actions-OIDC-Token automatisch
122
+ gegen einen kurzlebigen Publish-Token. (Klassische Access-Tokens mit
123
+ direktem Publish-Recht werden von npm ab Januar 2027 abgeschafft –
124
+ Trusted Publishing ist der empfohlene Ersatz.)
125
+ - als `@robin1053/project-cli` auf GitHub Packages (Auth über das
126
+ eingebaute `GITHUB_TOKEN`, kein Secret nötig)
127
+
128
+ Trusted Publishing lässt sich laut npm erst einrichten, wenn das Paket
129
+ mindestens einmal existiert – deshalb einmaliger manueller Bootstrap, danach
130
+ läuft alles Weitere über die CI ohne jedes Secret:
131
+
132
+ ```
133
+ npm run build
134
+ npm login
135
+ npm publish
136
+ npm trust github --allow-publish --file publish.yaml
137
+ ```
138
+
139
+ Ablauf für jede weitere Version: `version` in `package.json` erhöhen,
140
+ committen, Git-Tag + GitHub Release mit dieser Version anlegen – der
141
+ Workflow übernimmt den Rest.
142
+
143
+ ## Lizenz
144
+
145
+ [MIT](./LICENSE)
package/dist/index.js ADDED
@@ -0,0 +1,347 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import fs from "node:fs/promises";
4
+ import { Command } from "commander";
5
+ import * as p from "@clack/prompts";
6
+ import { execa } from "execa";
7
+ import { simpleGit, CheckRepoActions } from "simple-git";
8
+ import Conf from "conf";
9
+ import { Entry } from "@napi-rs/keyring";
10
+ const STACKS = [
11
+ {
12
+ id: "next",
13
+ label: "Next.js (TypeScript, App Router)",
14
+ command: (name) => ({
15
+ file: "npx",
16
+ args: ["create-next-app@latest", name, "--ts", "--app", "--yes"],
17
+ }),
18
+ },
19
+ {
20
+ id: "ts-lib",
21
+ label: "TypeScript Package (eigenes Template)",
22
+ template: "ts-lib",
23
+ },
24
+ {
25
+ id: "esp32",
26
+ label: "PlatformIO / ESP32 (Arduino)",
27
+ template: "esp32",
28
+ },
29
+ // TODO: hier deine weiteren Stacks eintragen (python, ...)
30
+ ];
31
+ class GiteaProvider {
32
+ baseUrl;
33
+ token;
34
+ constructor(baseUrl, token) {
35
+ this.baseUrl = baseUrl;
36
+ this.token = token;
37
+ }
38
+ async createRepo(name, isPrivate) {
39
+ const res = await fetch(`${this.baseUrl}/api/v1/user/repos`, {
40
+ method: "POST",
41
+ headers: {
42
+ Authorization: `token ${this.token}`,
43
+ "Content-Type": "application/json",
44
+ Accept: "application/json",
45
+ },
46
+ // auto_init: false -> leeres Repo, sonst scheitert der Push
47
+ body: JSON.stringify({ name, private: isPrivate, auto_init: false }),
48
+ });
49
+ if (res.status === 409)
50
+ throw new Error(`Repo "${name}" existiert bereits.`);
51
+ if (!res.ok)
52
+ throw new Error(`Gitea ${res.status}: ${await res.text()}`);
53
+ const repo = (await res.json());
54
+ return repo.clone_url;
55
+ }
56
+ }
57
+ class GitHubProvider {
58
+ token;
59
+ constructor(token) {
60
+ this.token = token;
61
+ }
62
+ async createRepo(name, isPrivate) {
63
+ const res = await fetch("https://api.github.com/user/repos", {
64
+ method: "POST",
65
+ headers: {
66
+ Authorization: `Bearer ${this.token}`,
67
+ Accept: "application/vnd.github+json",
68
+ "X-GitHub-Api-Version": "2026-03-10",
69
+ "Content-Type": "application/json",
70
+ },
71
+ body: JSON.stringify({ name, private: isPrivate, auto_init: false }),
72
+ });
73
+ // GitHub liefert 422 sowohl bei Namenskollision als auch bei Validierungsfehlern
74
+ if (res.status === 422) {
75
+ throw new Error(`Repo "${name}" existiert wohl schon.`);
76
+ }
77
+ if (!res.ok)
78
+ throw new Error(`GitHub ${res.status}: ${await res.text()}`);
79
+ const repo = (await res.json());
80
+ return repo.clone_url;
81
+ }
82
+ }
83
+ const config = new Conf({ projectName: "project-cli" });
84
+ const KEYRING_SERVICE = "project-cli";
85
+ function getStoredSecret(account) {
86
+ return new Entry(KEYRING_SERVICE, account).getPassword() ?? undefined;
87
+ }
88
+ function setStoredSecret(account, value) {
89
+ new Entry(KEYRING_SERVICE, account).setPassword(value);
90
+ }
91
+ async function ask(message) {
92
+ const answer = await p.password({ message });
93
+ if (p.isCancel(answer))
94
+ throw new Error("Abgebrochen.");
95
+ return answer;
96
+ }
97
+ async function askText(message) {
98
+ const answer = await p.text({ message });
99
+ if (p.isCancel(answer))
100
+ throw new Error("Abgebrochen.");
101
+ return answer;
102
+ }
103
+ async function resolveGitHubToken() {
104
+ const existing = process.env.GITHUB_TOKEN ?? getStoredSecret("github-token");
105
+ if (existing)
106
+ return existing;
107
+ const token = await ask("GitHub Personal Access Token (repo-Scope)");
108
+ setStoredSecret("github-token", token);
109
+ return token;
110
+ }
111
+ async function resolveGiteaCredentials() {
112
+ const existingUrl = process.env.GITEA_URL ?? config.get("giteaUrl");
113
+ const baseUrl = existingUrl ??
114
+ (await askText("Gitea-URL (z.B. https://gitea.example.com)"));
115
+ if (!existingUrl)
116
+ config.set("giteaUrl", baseUrl);
117
+ const existingToken = process.env.GITEA_TOKEN ?? getStoredSecret("gitea-token");
118
+ const token = existingToken ?? (await ask("Gitea Access Token (write:repository-Scope)"));
119
+ if (!existingToken)
120
+ setStoredSecret("gitea-token", token);
121
+ return [baseUrl, token];
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // 3. Die einzelnen Arbeitsschritte
125
+ // ---------------------------------------------------------------------------
126
+ async function scaffold(stack, targetDir, name) {
127
+ if (stack.command) {
128
+ const { file, args } = stack.command(name);
129
+ // cwd = Elternordner, weil der Generator den Zielordner selbst anlegt
130
+ await execa(file, args, { cwd: path.dirname(targetDir), stdio: "inherit" });
131
+ return;
132
+ }
133
+ if (stack.template) {
134
+ const src = path.join(import.meta.dirname, "templates", stack.template);
135
+ await fs.cp(src, targetDir, { recursive: true });
136
+ await restoreGitignore(targetDir);
137
+ await replacePlaceholders(targetDir, { projectName: name });
138
+ return;
139
+ }
140
+ throw new Error(`Stack ${stack.id} hat weder command noch template`);
141
+ }
142
+ // npm streicht jede Datei, die exakt ".gitignore" heißt, aus jedem Package
143
+ // (unabhängig vom Pfad) -> Templates lagern sie ohne Punkt als "gitignore"
144
+ // und wir benennen sie hier, nach dem Kopieren ins neue Projekt, zurück um.
145
+ async function restoreGitignore(targetDir) {
146
+ const from = path.join(targetDir, "gitignore");
147
+ const to = path.join(targetDir, ".gitignore");
148
+ await fs.rename(from, to).catch(() => { });
149
+ }
150
+ async function replacePlaceholders(dir, vars) {
151
+ const entries = await fs.readdir(dir, {
152
+ withFileTypes: true,
153
+ recursive: true,
154
+ });
155
+ for (const entry of entries) {
156
+ if (!entry.isFile())
157
+ continue;
158
+ const full = path.join(entry.parentPath, entry.name);
159
+ let content;
160
+ try {
161
+ content = await fs.readFile(full, "utf8");
162
+ }
163
+ catch {
164
+ continue; // Binärdatei o.ä. -> überspringen
165
+ }
166
+ let replaced = content;
167
+ for (const [key, value] of Object.entries(vars)) {
168
+ replaced = replaced.replaceAll(`{{${key}}}`, value);
169
+ }
170
+ if (replaced !== content)
171
+ await fs.writeFile(full, replaced, "utf8");
172
+ }
173
+ }
174
+ async function initGit(targetDir) {
175
+ const git = simpleGit(targetDir);
176
+ // IS_REPO_ROOT statt Default: Default prüft nur "irgendwo unter einem Repo",
177
+ // das wäre auch true, wenn targetDir zufällig innerhalb eines fremden
178
+ // Repos liegt -> init() würde übersprungen und add/commit liefen gegen
179
+ // das falsche (übergeordnete) Repo. Hier soll nur erkannt werden, ob
180
+ // targetDir selbst schon ein eigenes .git hat (z.B. von create-next-app).
181
+ const alreadyRepo = await git
182
+ .checkIsRepo(CheckRepoActions.IS_REPO_ROOT)
183
+ .catch(() => false);
184
+ if (!alreadyRepo)
185
+ await git.init();
186
+ await git.add(".");
187
+ await git.commit("chore: initial scaffold");
188
+ }
189
+ // ---------------------------------------------------------------------------
190
+ // 3b. PlatformIO-Helfer (build/upload/add-board)
191
+ // Ersatz für das commands.sh-Script aus dem esp32-Template-Vorbild:
192
+ // dort Bash + fzf, hier execa + @clack/prompts, damit es auch unter
193
+ // PowerShell/cmd läuft. Arbeiten immer auf der platformio.ini im cwd.
194
+ // ---------------------------------------------------------------------------
195
+ async function readPioEnvironments(cwd) {
196
+ const iniPath = path.join(cwd, "platformio.ini");
197
+ let content;
198
+ try {
199
+ content = await fs.readFile(iniPath, "utf8");
200
+ }
201
+ catch {
202
+ throw new Error(`Keine platformio.ini in ${cwd} gefunden.`);
203
+ }
204
+ return [...content.matchAll(/^\[env:([^\]]+)\]/gm)].map((m) => m[1]);
205
+ }
206
+ async function selectPioEnvironment(cwd, message) {
207
+ const envs = await readPioEnvironments(cwd);
208
+ if (envs.length === 0)
209
+ throw new Error("Keine [env:...]-Sektionen in platformio.ini gefunden.");
210
+ if (envs.length === 1)
211
+ return envs[0];
212
+ const choice = await p.select({
213
+ message,
214
+ options: envs.map((e) => ({ value: e, label: e })),
215
+ });
216
+ if (p.isCancel(choice))
217
+ throw new Error("Abgebrochen.");
218
+ return choice;
219
+ }
220
+ async function searchEsp32Boards(query) {
221
+ const { stdout } = await execa("pio", [
222
+ "boards",
223
+ "espressif32",
224
+ "--json-output",
225
+ ]);
226
+ const boards = JSON.parse(stdout);
227
+ if (!query.trim())
228
+ return boards;
229
+ const q = query.toLowerCase();
230
+ return boards.filter((b) => `${b.id} ${b.name}`.toLowerCase().includes(q));
231
+ }
232
+ async function addBoard(cwd) {
233
+ const query = await askText('Board suchen (z.B. "esp32-s3", leer = alle anzeigen)');
234
+ const boards = await searchEsp32Boards(query);
235
+ if (boards.length === 0)
236
+ throw new Error("Keine passenden Boards gefunden.");
237
+ const boardId = await p.select({
238
+ message: "Welches Board?",
239
+ options: boards
240
+ .slice(0, 50)
241
+ .map((b) => ({ value: b.id, label: `${b.name} (${b.id})` })),
242
+ });
243
+ if (p.isCancel(boardId))
244
+ throw new Error("Abgebrochen.");
245
+ const envName = await askText('Name der neuen Environment (z.B. "my-esp32-s3")');
246
+ const section = `\n[env:${envName}]\nplatform = espressif32\nboard = ${boardId}\n`;
247
+ await fs.appendFile(path.join(cwd, "platformio.ini"), section, "utf8");
248
+ }
249
+ // ---------------------------------------------------------------------------
250
+ // 4. Der eigentliche Ablauf
251
+ // ---------------------------------------------------------------------------
252
+ async function run(nameArg, opts) {
253
+ p.intro("Projekt-Setup");
254
+ const name = nameArg ??
255
+ (await p.text({
256
+ message: "Wie soll das Projekt heißen?",
257
+ validate: (v) => (v?.trim() ? undefined : "Name darf nicht leer sein"),
258
+ }));
259
+ if (p.isCancel(name))
260
+ return p.cancel("Abgebrochen.");
261
+ const stackId = await p.select({
262
+ message: "Welcher Stack?",
263
+ options: STACKS.map((s) => ({ value: s.id, label: s.label })),
264
+ });
265
+ if (p.isCancel(stackId))
266
+ return p.cancel("Abgebrochen.");
267
+ const remoteChoice = await p.select({
268
+ message: "Remote-Repository anlegen?",
269
+ options: [
270
+ { value: "none", label: "Nein, nur lokal" },
271
+ { value: "github", label: "GitHub" },
272
+ { value: "gitea", label: "Gitea" },
273
+ ],
274
+ });
275
+ if (p.isCancel(remoteChoice))
276
+ return p.cancel("Abgebrochen.");
277
+ const stack = STACKS.find((s) => s.id === stackId);
278
+ const targetDir = path.resolve(process.cwd(), name);
279
+ // Nicht in einen vorhandenen Ordner schreiben
280
+ const exists = await fs.stat(targetDir).then(() => true, () => false);
281
+ if (exists) {
282
+ p.cancel(`Ordner "${name}" existiert bereits.`);
283
+ return;
284
+ }
285
+ const s = p.spinner();
286
+ s.start("Grundgerüst wird angelegt");
287
+ await scaffold(stack, targetDir, name);
288
+ s.stop("Grundgerüst steht");
289
+ s.start("Git wird initialisiert");
290
+ await initGit(targetDir);
291
+ s.stop("Erster Commit ist da");
292
+ if (remoteChoice !== "none") {
293
+ // Zugangsdaten zuerst einsammeln (interaktiv, falls nötig) — danach erst
294
+ // den Spinner starten, sonst überlagern sich Prompt und Spinner.
295
+ const provider = remoteChoice === "gitea"
296
+ ? new GiteaProvider(...(await resolveGiteaCredentials()))
297
+ : new GitHubProvider(await resolveGitHubToken());
298
+ s.start("Remote wird angelegt");
299
+ const cloneUrl = await provider.createRepo(name, opts.private ?? false);
300
+ const git = simpleGit(targetDir);
301
+ await git.addRemote("origin", cloneUrl);
302
+ await git.push(["-u", "origin", "HEAD"]);
303
+ s.stop(`Gepusht nach ${cloneUrl}`);
304
+ }
305
+ p.outro(`Fertig. cd ${name}`);
306
+ }
307
+ // ---------------------------------------------------------------------------
308
+ // 5. Einstiegspunkt
309
+ // ---------------------------------------------------------------------------
310
+ const program = new Command();
311
+ program
312
+ .name("myinit")
313
+ .description("Legt neue Projekte inkl. Repo an")
314
+ .argument("[name]", "Projektname")
315
+ .option("-p, --private", "Remote-Repo privat anlegen")
316
+ .action(run);
317
+ // Die folgenden Befehle arbeiten auf einem bestehenden PlatformIO-Projekt
318
+ // im aktuellen Arbeitsverzeichnis (nicht auf dem Scaffold-Flow oben).
319
+ program
320
+ .command("build [env]")
321
+ .description("PlatformIO-Firmware bauen (pio run)")
322
+ .action(async (env) => {
323
+ const cwd = process.cwd();
324
+ const selected = env ?? (await selectPioEnvironment(cwd, "Welche Environment bauen?"));
325
+ await execa("pio", ["run", "-e", selected], { cwd, stdio: "inherit" });
326
+ });
327
+ program
328
+ .command("upload [env]")
329
+ .description("Firmware bauen und aufs Board flashen (pio run -t upload)")
330
+ .action(async (env) => {
331
+ const cwd = process.cwd();
332
+ const selected = env ?? (await selectPioEnvironment(cwd, "Welche Environment flashen?"));
333
+ await execa("pio", ["run", "-e", selected, "-t", "upload"], {
334
+ cwd,
335
+ stdio: "inherit",
336
+ });
337
+ });
338
+ program
339
+ .command("add-board")
340
+ .description("Neues ESP32-Board zur platformio.ini hinzufügen")
341
+ .action(async () => {
342
+ await addBoard(process.cwd());
343
+ });
344
+ program.parseAsync().catch((err) => {
345
+ console.error(err instanceof Error ? err.message : err);
346
+ process.exit(1);
347
+ });
@@ -0,0 +1 @@
1
+ * text=auto
@@ -0,0 +1,31 @@
1
+ name: PlatformIO CI
2
+
3
+ on:
4
+ push:
5
+ paths:
6
+ - 'include/**'
7
+ - 'src/**'
8
+ - 'lib/**'
9
+ - 'test/**'
10
+ - 'platformio.ini'
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v3
18
+ - uses: actions/cache@v3
19
+ with:
20
+ path: |
21
+ ~/.cache/pip
22
+ ~/.platformio/.cache
23
+ key: ${{ runner.os }}-pio
24
+ - uses: actions/setup-python@v4
25
+ with:
26
+ python-version: '3.9'
27
+ - name: Install PlatformIO Core
28
+ run: pip install --upgrade platformio
29
+
30
+ - name: Build PlatformIO Project
31
+ run: pio run
@@ -0,0 +1,30 @@
1
+ # {{projectName}}
2
+
3
+ PlatformIO-Projekt für ESP32 (Arduino-Framework).
4
+
5
+ ## Setup
6
+
7
+ 1. [PlatformIO Core](https://docs.platformio.org/en/latest/core/installation/index.html) installieren.
8
+ 2. Board per USB anschließen.
9
+
10
+ ## Bauen & Flashen
11
+
12
+ Über project-cli (im Projektordner ausgeführt):
13
+
14
+ node <pfad-zu-project-cli>/index.ts build [env]
15
+ node <pfad-zu-project-cli>/index.ts upload [env]
16
+ node <pfad-zu-project-cli>/index.ts add-board
17
+
18
+ `env` ist optional — ohne Angabe wird interaktiv aus den in `platformio.ini`
19
+ definierten `[env:...]`-Sektionen ausgewählt.
20
+
21
+ Oder direkt über PlatformIO:
22
+
23
+ pio run -e esp32dev
24
+ pio run -e esp32dev -t upload
25
+ pio device monitor
26
+
27
+ ## Boards
28
+
29
+ Vordefiniert: `esp32dev`, `featheresp32`, `adafruit_feather_esp32s2`.
30
+ Weitere über `add-board` oder manuell in `platformio.ini` ergänzen.
@@ -0,0 +1,37 @@
1
+ # Prerequisites
2
+ *.d
3
+
4
+ # Compiled Object files
5
+ *.slo
6
+ *.lo
7
+ *.o
8
+ *.obj
9
+
10
+ # Precompiled Headers
11
+ *.gch
12
+ *.pch
13
+
14
+ # Compiled Dynamic libraries
15
+ *.so
16
+ *.dylib
17
+ *.dll
18
+
19
+ # Fortran module files
20
+ *.mod
21
+ *.smod
22
+
23
+ # Compiled Static libraries
24
+ *.lai
25
+ *.la
26
+ *.a
27
+ *.lib
28
+
29
+ # Executables
30
+ *.exe
31
+ *.out
32
+ *.app
33
+
34
+ .pio
35
+ .cache
36
+
37
+ compile_commands.json
@@ -0,0 +1,39 @@
1
+
2
+ This directory is intended for project header files.
3
+
4
+ A header file is a file containing C declarations and macro definitions
5
+ to be shared between several project source files. You request the use of a
6
+ header file in your project source file (C, C++, etc) located in `src` folder
7
+ by including it, with the C preprocessing directive `#include'.
8
+
9
+ ```src/main.c
10
+
11
+ #include "header.h"
12
+
13
+ int main (void)
14
+ {
15
+ ...
16
+ }
17
+ ```
18
+
19
+ Including a header file produces the same results as copying the header file
20
+ into each source file that needs it. Such copying would be time-consuming
21
+ and error-prone. With a header file, the related declarations appear
22
+ in only one place. If they need to be changed, they can be changed in one
23
+ place, and programs that include the header file will automatically use the
24
+ new version when next recompiled. The header file eliminates the labor of
25
+ finding and changing all the copies as well as the risk that a failure to
26
+ find one copy will result in inconsistencies within a program.
27
+
28
+ In C, the usual convention is to give header files names that end with `.h'.
29
+ It is most portable to use only letters, digits, dashes, and underscores in
30
+ header file names, and at most one dot.
31
+
32
+ Read more about using header files in official GCC documentation:
33
+
34
+ * Include Syntax
35
+ * Include Operation
36
+ * Once-Only Headers
37
+ * Computed Includes
38
+
39
+ https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
@@ -0,0 +1,46 @@
1
+
2
+ This directory is intended for project specific (private) libraries.
3
+ PlatformIO will compile them to static libraries and link into executable file.
4
+
5
+ The source code of each library should be placed in an own separate directory
6
+ ("lib/your_library_name/[here are source files]").
7
+
8
+ For example, see a structure of the following two libraries `Foo` and `Bar`:
9
+
10
+ |--lib
11
+ | |
12
+ | |--Bar
13
+ | | |--docs
14
+ | | |--examples
15
+ | | |--src
16
+ | | |- Bar.c
17
+ | | |- Bar.h
18
+ | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
19
+ | |
20
+ | |--Foo
21
+ | | |- Foo.c
22
+ | | |- Foo.h
23
+ | |
24
+ | |- README --> THIS FILE
25
+ |
26
+ |- platformio.ini
27
+ |--src
28
+ |- main.c
29
+
30
+ and a contents of `src/main.c`:
31
+ ```
32
+ #include <Foo.h>
33
+ #include <Bar.h>
34
+
35
+ int main (void)
36
+ {
37
+ ...
38
+ }
39
+
40
+ ```
41
+
42
+ PlatformIO Library Dependency Finder will find automatically dependent
43
+ libraries scanning project source files.
44
+
45
+ More information about PlatformIO Library Dependency Finder
46
+ - https://docs.platformio.org/page/librarymanager/ldf.html
@@ -0,0 +1,39 @@
1
+ ; PlatformIO-Konfiguration für {{projectName}}
2
+ ;
3
+ ; Nur ESP32-Boards. Weitere Boards über `add-board` (project-cli) oder
4
+ ; manuell per `pio boards espressif32` suchen und [env:...]-Sektion ergänzen.
5
+
6
+ [platformio]
7
+ description = {{projectName}}
8
+ default_envs = esp32dev
9
+
10
+ [env]
11
+ framework = arduino
12
+
13
+ ; C++17 statt PlatformIO-Default gnu++11
14
+ build_unflags = -std=gnu++11
15
+ build_flags = -std=gnu++17 -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-function -Wno-format-extra-args
16
+
17
+ ; clang-tidy für `pio check`
18
+ check_tool = clangtidy
19
+
20
+ ; Serial-Monitor-Baudrate, muss zu Serial.begin() im Code passen
21
+ monitor_speed = 115200
22
+
23
+ lib_deps =
24
+ # Zum Einkommentieren, Beispiele:
25
+ # dxinteractive/ResponsiveAnalogRead ; glättet Analogwerte (Poti, Sensoren)
26
+ # thomasfredericks/Bounce2 ; Taster entprellen
27
+ # arkhipenko/TaskScheduler ; kooperatives Task-Scheduling
28
+
29
+ [env:esp32dev]
30
+ platform = espressif32
31
+ board = esp32dev
32
+
33
+ [env:featheresp32]
34
+ platform = espressif32
35
+ board = featheresp32
36
+
37
+ [env:adafruit_feather_esp32s2]
38
+ platform = espressif32
39
+ board = adafruit_feather_esp32s2
@@ -0,0 +1,10 @@
1
+ #include <Arduino.h>
2
+
3
+ // {{projectName}}
4
+
5
+ void setup() {
6
+ Serial.begin(115200);
7
+ }
8
+
9
+ void loop() {
10
+ }
@@ -0,0 +1,11 @@
1
+
2
+ This directory is intended for PlatformIO Test Runner and project tests.
3
+
4
+ Unit Testing is a software testing method by which individual units of
5
+ source code, sets of one or more MCU program modules together with associated
6
+ control data, usage procedures, and operating procedures, are tested to
7
+ determine whether they are fit for use. Unit testing finds problems early
8
+ in the development cycle.
9
+
10
+ More information about PlatformIO Unit Testing:
11
+ - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
@@ -0,0 +1,6 @@
1
+ # {{projectName}}
2
+
3
+ ## Entwicklung
4
+
5
+ npm install
6
+ npm run dev
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "dev": "node --watch src/index.ts"
9
+ },
10
+ "devDependencies": {
11
+ "@types/node": "^26.0.0",
12
+ "typescript": "^5.7.0"
13
+ }
14
+ }
@@ -0,0 +1 @@
1
+ console.log("{{projectName}} läuft");
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "nodenext",
5
+ "moduleResolution": "nodenext",
6
+ "types": ["node"],
7
+ "strict": true,
8
+ "outDir": "dist",
9
+ "rootDir": "src"
10
+ },
11
+ "include": ["src/**/*.ts"],
12
+ "exclude": ["node_modules", "dist"]
13
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@robineb/project-cli",
3
+ "version": "1.0.0",
4
+ "description": "My own Project cli tool to start projecs",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Robin1053/Project-CLI.git"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "main": "./dist/index.js",
14
+ "scripts": {
15
+ "build": "tsc && node scripts/copy-templates.mjs",
16
+ "test": "echo \"Error: no test specified\" && exit 1"
17
+ },
18
+ "bin": {
19
+ "project-cli": "dist/index.js"
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "type": "module",
25
+ "dependencies": {
26
+ "@clack/prompts": "^1.8.1",
27
+ "@napi-rs/keyring": "^2.1.0",
28
+ "commander": "^15.0.0",
29
+ "conf": "^15.1.0",
30
+ "execa": "^10.0.1",
31
+ "simple-git": "^3.36.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^26.6.0",
35
+ "tsx": "^4.23.13",
36
+ "typescript": "^7.0.2"
37
+ },
38
+ "allowScripts": {
39
+ "esbuild@0.28.2": true
40
+ }
41
+ }