create-deck-plugin 0.15.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.mjs ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ // Scaffold a Deck plugin.
3
+ //
4
+ // npm create deck-plugin my-thing
5
+ //
6
+ // Writes a folder that already builds, already has a git repository, and already knows how to
7
+ // install itself into Deck. The real work is in deck-plugin-sdk, which this installs — a
8
+ // scaffold on its own would freeze the shim and the types at the moment of creation, which is
9
+ // the thing that rots.
10
+ import { execFileSync } from "node:child_process";
11
+ import { cpSync, existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
12
+ import { dirname, join, resolve } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const here = dirname(fileURLToPath(import.meta.url));
16
+
17
+ const raw = process.argv[2];
18
+ if (!raw) {
19
+ console.error("usage: npm create deck-plugin <name>");
20
+ process.exit(1);
21
+ }
22
+
23
+ // The id is a config key and a path component in Deck, so it is checked here rather than at
24
+ // install time. Same rule the loader applies.
25
+ const id = raw.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
26
+ if (!id || !/^[a-z0-9]/.test(id)) {
27
+ console.error(`"${raw}" does not reduce to a usable id (letters, digits and dashes)`);
28
+ process.exit(1);
29
+ }
30
+
31
+ const dir = resolve(process.cwd(), id);
32
+ if (existsSync(dir) && readdirSync(dir).length) {
33
+ console.error(`${dir} already exists and is not empty`);
34
+ process.exit(1);
35
+ }
36
+
37
+ const name = id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
38
+ const component = name.replace(/[^A-Za-z0-9]/g, "") || "Plugin";
39
+
40
+ // The template is the SDK's — one copy, so the scaffold and the thing it installs cannot
41
+ // disagree. Resolved through node rather than by relative path, because once published these
42
+ // are two packages rather than two folders.
43
+ const sdkTemplate = existsSync(join(here, "template"))
44
+ ? join(here, "template") // published: copied in at pack time
45
+ : join(here, "..", "sdk", "template"); // in the repo: the SDK's own
46
+ cpSync(sdkTemplate, dir, { recursive: true });
47
+ // npm refuses to publish a file called .gitignore, so the template ships it renamed.
48
+ if (existsSync(join(dir, "gitignore"))) renameSync(join(dir, "gitignore"), join(dir, ".gitignore"));
49
+
50
+ for (const rel of ["plugin.json", "package.json", "src/plugin.tsx", "README.md"]) {
51
+ const p = join(dir, rel);
52
+ if (!existsSync(p)) continue;
53
+ writeFileSync(p, readFileSync(p, "utf8")
54
+ .replaceAll("__ID__", id)
55
+ .replaceAll("__NAME__", name)
56
+ .replaceAll("__COMPONENT__", component));
57
+ }
58
+
59
+ const run = (cmd, args) => {
60
+ try { execFileSync(cmd, args, { cwd: dir, stdio: "inherit" }); return true; }
61
+ catch { return false; }
62
+ };
63
+
64
+ console.log(`\nInstalling deck-plugin-sdk…`);
65
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
66
+ const installed = run(npm, ["install", "--save-dev", "deck-plugin-sdk",
67
+ "react", "lucide-react", "@types/react"]);
68
+
69
+ // A git repository from the start, because a plugin's home is its own repository — that is how
70
+ // it gets published, and how it gets listed in the registry.
71
+ run("git", ["init", "-q", "-b", "main"]);
72
+
73
+ console.log(`\n${name} is in ${id}/\n`);
74
+ console.log(" cd " + id);
75
+ if (!installed) console.log(" npm install # this failed; run it yourself");
76
+ console.log(" npm run dev # build, and rebuild as you edit");
77
+ console.log(" npm run install-local # copy it into Deck and load it\n");
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "create-deck-plugin",
3
+ "version": "0.15.1",
4
+ "description": "Scaffold a Deck plugin.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "create-deck-plugin": "index.mjs"
9
+ },
10
+ "files": [
11
+ "index.mjs",
12
+ "template"
13
+ ],
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "scripts": {
18
+ "prepack": "node prepack.mjs"
19
+ }
20
+ }
@@ -0,0 +1,17 @@
1
+ # __NAME__
2
+
3
+ A plugin for [Deck](https://deck.hasan-zarif.com).
4
+
5
+ ## Developing
6
+
7
+ ```sh
8
+ npm run dev # build, and rebuild as you edit
9
+ npm run install-local # copy it into Deck, which loads it without a restart
10
+ ```
11
+
12
+ `src/plugin.tsx` is the entry point; its default export is the tab.
13
+
14
+ ## What it does
15
+
16
+ Say what this plugin is for. This file is what the registry shows on its page, so it is worth
17
+ writing for somebody deciding whether to install it rather than for yourself.
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ # Copied in by the build from the SDK — not yours to edit, and not worth committing.
3
+ shim/
4
+ types/
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "__ID__",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "build": "deck-plugin build",
7
+ "dev": "deck-plugin dev",
8
+ "install-local": "deck-plugin install"
9
+ }
10
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "__ID__",
3
+ "name": "__NAME__",
4
+ "description": "One line on what this is for, shown under its name in Settings.",
5
+ "icon": "Sparkles",
6
+ "version": "0.1.0",
7
+ "minDeck": "0.15.0"
8
+ }
@@ -0,0 +1,65 @@
1
+ // __NAME__ — a Deck plugin.
2
+ //
3
+ // The default export is the tab. A plugin can export more than that — a footer readout, a
4
+ // settings panel, palette commands, a pane view — and `plugins/API.md` in the Deck repository
5
+ // lists every slot. One export is enough to start.
6
+ import { useState } from "react";
7
+ import { Sparkles } from "lucide-react";
8
+ import { configRead, configWrite } from "../shim/bridge.js";
9
+
10
+ /** Where this plugin's own settings live. The `plugin-` prefix keeps them on this machine. */
11
+ const CONFIG = "plugin-__ID__";
12
+
13
+ export default function __COMPONENT__() {
14
+ const [note, setNote] = useState("");
15
+ const [saved, setSaved] = useState(false);
16
+
17
+ // Deck's config store, the same one its own modules use. Reading a key that has never been
18
+ // written resolves to an empty string rather than throwing.
19
+ const load = async () => {
20
+ const raw = await configRead(CONFIG).catch(() => "");
21
+ setNote(raw.trim() ? (JSON.parse(raw) as { note?: string }).note ?? "" : "");
22
+ };
23
+
24
+ const save = async () => {
25
+ await configWrite(CONFIG, JSON.stringify({ note }));
26
+ setSaved(true);
27
+ setTimeout(() => setSaved(false), 1500);
28
+ };
29
+
30
+ return (
31
+ <div className="flex h-full flex-col gap-4 p-6">
32
+ <header className="flex items-center gap-3">
33
+ <Sparkles size={18} style={{ color: "var(--accent)" }} />
34
+ <h1 className="text-base font-semibold text-text-primary">__NAME__</h1>
35
+ </header>
36
+
37
+ <p className="max-w-[60ch] text-sm text-text-secondary">
38
+ Edit <code className="rounded bg-elev px-1.5 py-0.5 text-text-primary">src/plugin.tsx</code>,
39
+ run <code className="rounded bg-elev px-1.5 py-0.5 text-text-primary">npm run dev</code>,
40
+ and Deck reloads the plugin as you save.
41
+ </p>
42
+
43
+ <div className="flex max-w-md items-center gap-2">
44
+ <input
45
+ value={note}
46
+ onChange={(e) => setNote(e.target.value)}
47
+ onFocus={() => void load()}
48
+ placeholder="Type something, then save it"
49
+ className="min-w-0 flex-1 rounded-lg border border-subtle bg-elev px-3 py-2 text-sm text-text-primary outline-none focus:border-accent"
50
+ />
51
+ <button
52
+ onClick={() => void save()}
53
+ className="shrink-0 rounded-lg border border-subtle bg-elev px-3 py-2 text-sm text-text-secondary hover:border-accent hover:text-text-primary"
54
+ >
55
+ {saved ? "Saved" : "Save"}
56
+ </button>
57
+ </div>
58
+
59
+ <p className="text-xs text-text-muted">
60
+ That writes to Deck's config store under <code>{CONFIG}</code>, which stays on this
61
+ machine.
62
+ </p>
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "skipLibCheck": true,
10
+ "lib": ["ES2022", "DOM"]
11
+ },
12
+ "include": ["src/**/*"]
13
+ }