jdm-plugin-template 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/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # jdm-plugin-template
2
+
3
+ A starter template for building **jdm-cli** plugins. This repo is also self-bootstrapping — the `create` command clones this very repo into a new project, giving contributors a clean slate to start from.
4
+
5
+ ---
6
+
7
+ ## What is this?
8
+
9
+ `jdm-plugin-template` is the **official base template** for all jdm-cli plugins. It ships with a working plugin structure, a set of standard commands (`create`, `dev`, `build`, `clean`, `install`), shared logging helpers, and a version compatibility system — so you spend time building your plugin, not scaffolding it.
10
+
11
+ ---
12
+
13
+ ## Quick Start
14
+
15
+ ### Use this template to scaffold a new plugin
16
+
17
+ ```bash
18
+ # Install the CLI (if you haven't already)
19
+ npm install -g jdm-cli
20
+ jdm-cli add plugin-template
21
+
22
+ # Scaffold a new plugin project
23
+ jdm-cli plugin-template create
24
+
25
+ # Then follow the prompts, or use flags:
26
+ jdm-cli plugin-template create --name my-plugin
27
+ jdm-cli plugin-template create --name my-plugin --install
28
+ ```
29
+
30
+ This will:
31
+ 1. Clone this repo into a folder named `my-plugin` (or `.` for current dir)
32
+ 2. Strip `.git` so it's a clean project — not a fork
33
+ 3. Optionally run `npm install` for you
34
+
35
+ ---
36
+
37
+ ## Project Structure
38
+
39
+ ```
40
+ jdm-plugin-template/
41
+ ├── lib/
42
+ │ ├── index.js # Entry point — namespace, command map, dispatcher
43
+ │ ├── config.js # Config read/write and compatibility guard
44
+ │ ├── compat.js # Plugin version + per-command compatibility ranges
45
+ │ ├── logger.js # Shared logging helpers (ok, fail, warn, info, step, header)
46
+ │ └── commands/
47
+ │ ├── create.js # Scaffold a new project (clones this repo)
48
+ │ ├── dev.js # Start development environment
49
+ │ ├── build.js # Compile / package the project
50
+ │ ├── clean.js # Remove build artifacts
51
+ │ └── install.js # Install dependencies
52
+ ├── package.json
53
+ └── README.md
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Built-in Commands
59
+
60
+ | Command | Description | Key Flags |
61
+ |-----------|------------------------------------------|----------------------------------------|
62
+ | `create` | Scaffold a new project from this template | `--name <name>`, `--install` |
63
+ | `dev` | Start development environment | _(wire up your own processes)_ |
64
+ | `build` | Compile / package the project | `--frontend`, `--backend`, `--full` |
65
+ | `clean` | Remove build artifacts | `--dry` (preview without deleting) |
66
+ | `install` | Install dependencies | _(none)_ |
67
+
68
+ ---
69
+
70
+ ## Contributing / Building Your Own Plugin
71
+
72
+ This repo is the starting point. Here's how to get going:
73
+
74
+ ### 1. Scaffold a copy
75
+
76
+ ```bash
77
+ jdm-cli plugin-template create --name my-plugin --install
78
+ cd my-plugin
79
+ ```
80
+
81
+ Or clone manually:
82
+
83
+ ```bash
84
+ git clone https://github.com/JDM-Github/jdm-plugin-template my-plugin
85
+ cd my-plugin
86
+ rm -rf .git
87
+ npm install
88
+ ```
89
+
90
+ ### 2. Set your namespace
91
+
92
+ In **three places**, replace `"plugin-template"` with your plugin's name:
93
+
94
+ | File | What to change |
95
+ |---|---|
96
+ | `lib/index.js` | `export const namespace = "plugin-template"` |
97
+ | `lib/config.js` | `const PLUGIN_NAME = "plugin-template"` |
98
+ | `lib/logger.js` | `const ns = "plugin-template"` inside `header()` |
99
+ | `package.json` | `"name"`, `"jdmPlugin.namespace"`, `"jdmPlugin.description"` |
100
+
101
+ ### 3. Add your commands
102
+
103
+ 1. Create `lib/commands/my-command.js` with a default export
104
+ 2. Import it in `lib/index.js` and add it to the `commands` map
105
+ 3. If it needs interactive prompts (`ask(rl, ...)`), add the name to `INTERACTIVE_COMMANDS`
106
+ 4. Add it to `showDesign()` for the help screen
107
+ 5. Add it to the `jdmPlugin.commands` array in `package.json`
108
+
109
+ ### 4. Wire up `dev` and `build`
110
+
111
+ Both files have clearly marked `// TODO` blocks — drop in your actual build commands, server launchers, or watchers there. Cross-platform terminal helpers (Windows Terminal, CMD, gnome-terminal, osascript) are already included in `dev.js`.
112
+
113
+ ### 5. Update compatibility (when needed)
114
+
115
+ When you make breaking changes to the template structure, bump `COMPAT` in `lib/compat.js` so existing projects get a clear error instead of a silent failure:
116
+
117
+ ```js
118
+ // lib/compat.js
119
+ export const pluginVersion = "1.1.0";
120
+
121
+ export const COMPAT = {
122
+ global: ">=1.1.0", // bump when ALL commands need a newer project
123
+ commands: {
124
+ build: ">=1.1.0", // or target a specific command
125
+ },
126
+ };
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Logger Helpers
132
+
133
+ All commands share the same logging helpers from `lib/logger.js`:
134
+
135
+ ```js
136
+ import { ok, fail, warn, info, step, header, divider } from "../logger.js";
137
+
138
+ ok(chalk, "Thing worked"); // ✔ Thing worked (green)
139
+ fail(chalk, "Thing broke"); // ✖ Thing broke (red)
140
+ warn(chalk, "Watch out"); // ⚠ Watch out (yellow)
141
+ info(chalk, "Just so you know"); // · Just so you know (gray)
142
+
143
+ step(chalk, 1, 3, "Doing X"); // [1/3] Doing X
144
+ header(chalk, "my-command"); // jdm / plugin-template / my-command
145
+ divider(chalk); // ─────────────────────────────────────
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Config System
151
+
152
+ Every scaffolded project gets a `.jdm-config.json` file. This allows commands to verify they're running inside a compatible project:
153
+
154
+ ```json
155
+ {
156
+ "plugin-template": {
157
+ "pluginVersion": "1.0.0",
158
+ "createdAt": "2025-01-01T00:00:00.000Z",
159
+ "projectName": "my-plugin"
160
+ }
161
+ }
162
+ ```
163
+
164
+ Use `checkCompat(chalk, "command-name")` at the top of any command that requires a valid project context. It will warn if the config is missing, or error with a helpful message if the version is out of range.
165
+
166
+ ---
167
+
168
+ ## Requirements
169
+
170
+ - Node.js 18+
171
+ - Git (for the `create` command)
172
+ - jdm-cli installed globally
173
+
174
+ ---
175
+
176
+ ## License
177
+
178
+ MIT — [JDM-Github](https://github.com/JDM-Github)
package/bin/guard.js ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import chalk from "chalk";
3
+ import stringWidth from "string-width";
4
+ import stripAnsi from "strip-ansi";
5
+
6
+ const box = (lines) => {
7
+ const cleaned = lines.map(l => stripAnsi(l));
8
+
9
+ const width = Math.max(...cleaned.map(stringWidth));
10
+
11
+ const horizontal = width + 2;
12
+
13
+ const top = "┌" + "─".repeat(horizontal) + "┐";
14
+ const bottom = "└" + "─".repeat(horizontal) + "┘";
15
+
16
+ const mid = lines.map((l, i) => {
17
+ const rawWidth = stringWidth(cleaned[i]);
18
+ const pad = horizontal - rawWidth - 1;
19
+ return "│ " + l + " ".repeat(pad) + "│";
20
+ });
21
+ return [top, ...mid, bottom].join("\n");
22
+ };
23
+
24
+ const output = box([
25
+ chalk.red("⛔ Direct usage is not allowed."),
26
+ "",
27
+ "This package is a jdm-cli plugin-template.",
28
+ "Install jdm-cli first, then register this plugin:",
29
+ "",
30
+ "npm install -g jdm-cli",
31
+ "jdm-cli add plugin-template",
32
+ "",
33
+ "Then use it via:",
34
+ "jdm-cli plugin-template <command>",
35
+ ]);
36
+
37
+ console.log("\n" + output + "\n");
@@ -0,0 +1,109 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/commands/build.js
3
+ //
4
+ // The "build" command compiles / packages your project.
5
+ //
6
+ // This template shows:
7
+ // ✔ Multi-flag build stages (--frontend, --backend, --full)
8
+ // ✔ How to shell out to build tools (npm run build, etc.)
9
+ // ✔ How to copy output files to a destination folder
10
+ // ✔ Graceful error handling per stage
11
+ //
12
+ // Rename to compile.js / bundle.js / package.js — whatever
13
+ // makes sense for your plugin.
14
+ // ─────────────────────────────────────────────────────────────
15
+
16
+ import fs from "fs";
17
+ import path from "path";
18
+ import { execSync } from "child_process";
19
+ import { checkCompat } from "../config.js";
20
+ import { ok, fail, info, warn, step, header, divider } from "../logger.js";
21
+
22
+ // ─────────────────────────────────────────────────────────────
23
+ // Internal exec helper
24
+ // ─────────────────────────────────────────────────────────────
25
+ function exec(cmd, opts = {}) {
26
+ try {
27
+ execSync(cmd, { ...opts, stdio: "pipe" });
28
+ } catch (err) {
29
+ const out = [err.stdout?.toString(), err.stderr?.toString()].filter(Boolean).join("\n");
30
+ throw new Error(out || err.message);
31
+ }
32
+ }
33
+
34
+ // ─────────────────────────────────────────────────────────────
35
+ // Build stages (each is its own function — easy to compose)
36
+ // ─────────────────────────────────────────────────────────────
37
+
38
+ /**
39
+ * Example "frontend" stage.
40
+ * Replace with your actual frontend build command + output path.
41
+ */
42
+ function buildFrontend(chalk, root) {
43
+ // const frontendDir = path.join(root, "frontend");
44
+ // info(chalk, "Building frontend...");
45
+ // exec("npm run build", { cwd: frontendDir });
46
+ // ok(chalk, "Frontend built");
47
+
48
+ // Placeholder
49
+ info(chalk, "buildFrontend placeholder — wire up your build command.");
50
+ ok(chalk, "Frontend stage done (placeholder)");
51
+ }
52
+
53
+ /**
54
+ * Example "backend" stage.
55
+ * Replace with your backend compile/package command.
56
+ */
57
+ function buildBackend(chalk, root) {
58
+ // const backendDir = path.join(root, "backend");
59
+ // info(chalk, "Packaging backend...");
60
+ // exec("python build.py", { cwd: backendDir });
61
+ // ok(chalk, "Backend packaged");
62
+
63
+ info(chalk, "buildBackend placeholder — wire up your build command.");
64
+ ok(chalk, "Backend stage done (placeholder)");
65
+ }
66
+
67
+ // ─────────────────────────────────────────────────────────────
68
+ // Main export
69
+ // Signature: build(chalk, args)
70
+ // ─────────────────────────────────────────────────────────────
71
+ export default async function build(chalk, args = []) {
72
+ header(chalk, "build");
73
+
74
+ // ── Guard ─────────────────────────────────────────────────
75
+ if (!checkCompat(chalk, "build")) return;
76
+
77
+ // ── Parse flags ───────────────────────────────────────────
78
+ // --frontend build only the frontend
79
+ // --backend build only the backend
80
+ // --full build everything (same as --frontend --backend)
81
+ const doFrontend = args.includes("--frontend") || args.includes("--full");
82
+ const doBackend = args.includes("--backend") || args.includes("--full");
83
+ const doAll = !doFrontend && !doBackend; // no flags → build all
84
+
85
+ const root = process.cwd();
86
+ let stageN = 1;
87
+ const total = (doAll ? 2 : 0) + (doFrontend ? 1 : 0) + (doBackend ? 1 : 0);
88
+
89
+ try {
90
+ if (doFrontend || doAll) {
91
+ step(chalk, stageN++, total, "Frontend");
92
+ buildFrontend(chalk, root);
93
+ }
94
+
95
+ if (doBackend || doAll) {
96
+ step(chalk, stageN++, total, "Backend");
97
+ buildBackend(chalk, root);
98
+ }
99
+ } catch (err) {
100
+ fail(chalk, `Build failed: ${err.message}`);
101
+ return;
102
+ }
103
+
104
+ console.log();
105
+ divider(chalk);
106
+ console.log(chalk.green(" ✔ Build complete!"));
107
+ divider(chalk);
108
+ console.log();
109
+ }
@@ -0,0 +1,74 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/commands/clean.js
3
+ //
4
+ // The "clean" command removes build artifacts and temp files.
5
+ //
6
+ // This template shows:
7
+ // ✔ How to remove folders/files with existence checks
8
+ // ✔ How to give the user a dry-run option (--dry)
9
+ // ✔ Pattern for defining what to clean in one place
10
+ // ─────────────────────────────────────────────────────────────
11
+
12
+ import fs from "fs";
13
+ import path from "path";
14
+ import { checkCompat } from "../config.js";
15
+ import { ok, warn, info, header, divider } from "../logger.js";
16
+
17
+ // ── Define everything that should be cleaned here ─────────────
18
+ // Each entry is relative to process.cwd().
19
+ // Add/remove paths to match your plugin's output structure.
20
+ const CLEAN_TARGETS = [
21
+ "dist",
22
+ "build",
23
+ ".cache",
24
+ // "some-other-artifact-folder",
25
+ ];
26
+
27
+ // ─────────────────────────────────────────────────────────────
28
+ // Main export
29
+ // Signature: clean(chalk, args)
30
+ // ─────────────────────────────────────────────────────────────
31
+ export default async function clean(chalk, args = []) {
32
+ header(chalk, "clean");
33
+
34
+ // ── Guard: must be inside a valid project ─────────────────
35
+ if (!checkCompat(chalk, "clean")) return;
36
+
37
+ // ── Flags ─────────────────────────────────────────────────
38
+ // --dry Print what would be deleted without deleting it
39
+ const dry = args.includes("--dry");
40
+ if (dry) info(chalk, chalk.yellow("Dry run — nothing will be deleted"));
41
+
42
+ const root = process.cwd();
43
+ let removed = 0;
44
+
45
+ for (const target of CLEAN_TARGETS) {
46
+ const fullPath = path.join(root, target);
47
+
48
+ if (!fs.existsSync(fullPath)) {
49
+ // Skip silently — already clean
50
+ continue;
51
+ }
52
+
53
+ if (dry) {
54
+ warn(chalk, `Would remove: ${chalk.cyan(target)}`);
55
+ } else {
56
+ fs.rmSync(fullPath, { recursive: true, force: true });
57
+ ok(chalk, `Removed ${chalk.cyan(target)}`);
58
+ removed++;
59
+ }
60
+ }
61
+
62
+ if (removed === 0 && !dry) {
63
+ info(chalk, "Nothing to clean.");
64
+ }
65
+
66
+ console.log();
67
+ divider(chalk);
68
+ console.log(dry
69
+ ? chalk.yellow(" ⚠ Dry run complete — no files deleted")
70
+ : chalk.green(" ✔ Clean complete!")
71
+ );
72
+ divider(chalk);
73
+ console.log();
74
+ }
@@ -0,0 +1,228 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/commands/create.js
3
+ //
4
+ // The "create" command scaffolds a new project for your plugin.
5
+ //
6
+ // This template shows:
7
+ // ✔ How to use the header / step / ok / fail / info helpers
8
+ // ✔ How to prompt the user interactively with rl (readline)
9
+ // ✔ How to use the --name flag to skip the prompt
10
+ // ✔ How to clone a GitHub repo and strip .git
11
+ // ✔ How to write a local config file (.jdm-config.json)
12
+ // ✔ How to use an install.log for error output
13
+ //
14
+ // Remove or replace anything you don't need.
15
+ // ─────────────────────────────────────────────────────────────
16
+
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import { execSync } from "child_process";
20
+ import { writeConfig } from "../config.js";
21
+ import { ok, fail, warn, info, step, header, divider } from "../logger.js";
22
+
23
+ // ── Replace this with your actual GitHub template repo URL ────
24
+ const TEMPLATE_REPO = "https://github.com/JDM-Github/jdm-plugin-template";
25
+
26
+ // ── Folders that would conflict in cwd install ────────────────
27
+ const CONFLICT_FOLDERS = ["src", "dist"]; // ← adjust as needed
28
+
29
+ // ─────────────────────────────────────────────────────────────
30
+ // Install log (written to <targetDir>/install.log on error)
31
+ // ─────────────────────────────────────────────────────────────
32
+ let logPath = null;
33
+
34
+ function initLog(targetDir) {
35
+ logPath = path.join(targetDir, "install.log");
36
+ fs.writeFileSync(logPath, `[install log — ${new Date().toISOString()}]\n\n`, "utf8");
37
+ }
38
+
39
+ function appendLog(line) {
40
+ if (logPath) fs.appendFileSync(logPath, line + "\n", "utf8");
41
+ }
42
+
43
+ function cleanLog() {
44
+ if (logPath && fs.existsSync(logPath)) {
45
+ fs.unlinkSync(logPath);
46
+ logPath = null;
47
+ }
48
+ }
49
+
50
+ // ─────────────────────────────────────────────────────────────
51
+ // Exec wrapper (captures stdout/stderr into install.log)
52
+ // ─────────────────────────────────────────────────────────────
53
+ function exec(cmd, opts = {}) {
54
+ try {
55
+ const result = execSync(cmd, { ...opts, stdio: "pipe" });
56
+ if (result) appendLog(`[OK] ${cmd}\n${result.toString()}`);
57
+ return result;
58
+ } catch (err) {
59
+ appendLog([
60
+ `[FAIL] ${cmd}`,
61
+ err.stdout?.toString() ?? "",
62
+ err.stderr?.toString() ?? "",
63
+ ].join("\n"));
64
+ throw err;
65
+ }
66
+ }
67
+
68
+ // ─────────────────────────────────────────────────────────────
69
+ // rl helper (wraps readline.question as a Promise)
70
+ // ─────────────────────────────────────────────────────────────
71
+ function ask(rl, question) {
72
+ return new Promise((resolve) => rl.question(question, resolve));
73
+ }
74
+
75
+ // ─────────────────────────────────────────────────────────────
76
+ // Main export
77
+ // Signature must match what index.js passes:
78
+ // create(chalk, rl, args)
79
+ // ─────────────────────────────────────────────────────────────
80
+ export default async function create(chalk, rl, args = []) {
81
+ header(chalk, "create");
82
+
83
+ // ── Parse flags ───────────────────────────────────────────
84
+ // --name my-project → skip the name prompt
85
+ // --install → run npm install / pip install after clone
86
+ const nameIdx = args.indexOf("--name");
87
+ const nameArg = nameIdx !== -1 ? args[nameIdx + 1] : null;
88
+ const shouldInstall = args.includes("--install");
89
+
90
+ // ── Step 1: resolve target directory ─────────────────────
91
+ step(chalk, 1, 3, "Target Directory");
92
+
93
+ const answer = nameArg
94
+ ?? (await ask(rl, chalk.white("\n Project name (or . for current folder): "))).trim();
95
+
96
+ let targetDir;
97
+
98
+ if (answer === ".") {
99
+ // ── Install into current directory ────────────────────
100
+ targetDir = process.cwd();
101
+ info(chalk, `Using current directory: ${chalk.cyan(targetDir)}`);
102
+
103
+ const entries = fs.readdirSync(targetDir);
104
+ const conflicts = entries.filter(
105
+ (e) => CONFLICT_FOLDERS.includes(e) && fs.statSync(path.join(targetDir, e)).isDirectory()
106
+ );
107
+
108
+ if (conflicts.length > 0) {
109
+ fail(chalk, `Conflicting folders: ${conflicts.map((c) => chalk.red(c)).join(", ")}`);
110
+ const confirm = (await ask(rl, chalk.white(" Remove them and continue? [y/N]: "))).trim().toLowerCase();
111
+ if (confirm !== "y") {
112
+ console.log(chalk.gray("\n Aborted.\n"));
113
+ return;
114
+ }
115
+ for (const c of conflicts) {
116
+ fs.rmSync(path.join(targetDir, c), { recursive: true, force: true });
117
+ ok(chalk, `Removed ${chalk.red(c)}`);
118
+ }
119
+ } else if (entries.length > 0) {
120
+ warn(chalk, "Current folder is not empty.");
121
+ const confirm = (await ask(rl, chalk.white(" Continue anyway? [y/N]: "))).trim().toLowerCase();
122
+ if (confirm !== "y") {
123
+ console.log(chalk.gray("\n Aborted.\n"));
124
+ return;
125
+ }
126
+ }
127
+
128
+ } else {
129
+ // ── Create a named subfolder ──────────────────────────
130
+ if (!answer || answer.includes("/") || answer.includes("\\")) {
131
+ fail(chalk, "Invalid project name.");
132
+ return;
133
+ }
134
+ targetDir = path.join(process.cwd(), answer);
135
+ if (fs.existsSync(targetDir)) {
136
+ warn(chalk, `Folder ${chalk.cyan(answer)} already exists.`);
137
+ const confirm = (await ask(rl, chalk.white(" Continue anyway? [y/N]: "))).trim().toLowerCase();
138
+ if (confirm !== "y") {
139
+ console.log(chalk.gray("\n Aborted.\n"));
140
+ return;
141
+ }
142
+ } else {
143
+ fs.mkdirSync(targetDir, { recursive: true });
144
+ ok(chalk, `Created folder: ${chalk.cyan(targetDir)}`);
145
+ }
146
+ }
147
+
148
+ initLog(targetDir);
149
+
150
+ // ── Step 2: clone template ────────────────────────────────
151
+ step(chalk, 2, 3, "Cloning template");
152
+
153
+ try {
154
+ info(chalk, `Cloning from ${chalk.cyan(TEMPLATE_REPO)}...`);
155
+
156
+ // Clone into a temp subfolder first to avoid git's "already exists
157
+ // and is not an empty directory" error — which always fires when the
158
+ // template repo is cloning itself into its own working directory.
159
+ const tmpDir = path.join(targetDir, "__jdm_tmp__");
160
+ if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true });
161
+
162
+ exec(`git clone ${TEMPLATE_REPO} "${tmpDir}"`);
163
+
164
+ // Strip .git so this becomes a clean project, not a fork
165
+ const gitDir = path.join(tmpDir, ".git");
166
+ if (fs.existsSync(gitDir)) {
167
+ fs.rmSync(gitDir, { recursive: true, force: true });
168
+ info(chalk, "Removed .git (clean slate)");
169
+ }
170
+
171
+ // Move all cloned files from tmpDir into targetDir,
172
+ // overwriting anything that was already there.
173
+ for (const entry of fs.readdirSync(tmpDir)) {
174
+ const src = path.join(tmpDir, entry);
175
+ const dest = path.join(targetDir, entry);
176
+ if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
177
+ fs.renameSync(src, dest);
178
+ }
179
+ fs.rmSync(tmpDir, { recursive: true, force: true });
180
+
181
+ ok(chalk, "Template cloned");
182
+ } catch (err) {
183
+ fail(chalk, `Clone failed: ${err.message}`);
184
+ console.log(chalk.yellow("\n Full output written to: ") + chalk.white("install.log"));
185
+ return;
186
+ }
187
+
188
+ // ── Step 3: optional dependency install ──────────────────
189
+ step(chalk, 3, 3, "Dependencies");
190
+
191
+ if (shouldInstall) {
192
+ try {
193
+ info(chalk, "Running npm install...");
194
+ exec("npm install", { cwd: targetDir });
195
+ ok(chalk, "Dependencies installed");
196
+ } catch (err) {
197
+ fail(chalk, `npm install failed: ${err.message}`);
198
+ console.log(chalk.yellow("\n Full output written to: ") + chalk.white("install.log"));
199
+ return;
200
+ }
201
+ } else {
202
+ info(chalk, "Skipped (pass --install to auto-install)");
203
+ }
204
+
205
+ // ── Write local config ────────────────────────────────────
206
+ writeConfig(targetDir, { projectName: path.basename(targetDir) });
207
+ info(chalk, `Created ${chalk.cyan(".jdm-config.json")}`);
208
+
209
+ cleanLog();
210
+
211
+ // ── Done ──────────────────────────────────────────────────
212
+ console.log();
213
+ divider(chalk);
214
+ console.log(chalk.green(" ✔ Project ready!"));
215
+ console.log(chalk.gray(` Location: ${targetDir}`));
216
+ divider(chalk);
217
+ console.log();
218
+
219
+ if (!shouldInstall) {
220
+ console.log(chalk.white(" Next steps:"));
221
+ console.log(chalk.gray(" jdm-cli <namespace> install → install dependencies"));
222
+ console.log(chalk.gray(" jdm-cli <namespace> dev → start development"));
223
+ } else {
224
+ console.log(chalk.white(" Next steps:"));
225
+ console.log(chalk.gray(" jdm-cli <namespace> dev → start development"));
226
+ }
227
+ console.log();
228
+ }
@@ -0,0 +1,147 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/commands/dev.js
3
+ //
4
+ // The "dev" command starts your development environment.
5
+ //
6
+ // This template shows:
7
+ // ✔ How to guard a command with checkCompat
8
+ // ✔ How to spawn processes in new terminal windows
9
+ // (Windows Terminal → CMD fallback → Unix terminals)
10
+ // ✔ How to allocate free ports dynamically
11
+ // ✔ How to pass env vars to spawned processes
12
+ //
13
+ // Replace the process launch blocks with whatever your plugin
14
+ // needs to start (servers, watchers, proxies, etc.)
15
+ // ─────────────────────────────────────────────────────────────
16
+
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import net from "net";
20
+ import { spawn } from "child_process";
21
+ import { checkCompat } from "../config.js";
22
+ import { ok, fail, info, header, divider } from "../logger.js";
23
+
24
+ // ─────────────────────────────────────────────────────────────
25
+ // Port allocation
26
+ // ─────────────────────────────────────────────────────────────
27
+
28
+ /** Returns a free port on 127.0.0.1 by binding to port 0. */
29
+ function getFreePort() {
30
+ return new Promise((resolve, reject) => {
31
+ const server = net.createServer();
32
+ server.listen(0, "127.0.0.1", () => {
33
+ const port = server.address().port;
34
+ server.close(() => resolve(port));
35
+ });
36
+ server.on("error", reject);
37
+ });
38
+ }
39
+
40
+ // ─────────────────────────────────────────────────────────────
41
+ // Cross-platform terminal launchers
42
+ // ─────────────────────────────────────────────────────────────
43
+
44
+ /**
45
+ * Launch a command in a new Windows Terminal tab.
46
+ * Falls back to launchInCmdWindow if wt.exe isn't available.
47
+ */
48
+ function launchInWindowsTerminal(title, cwd, command, args, env = {}) {
49
+ const envPrefix = Object.entries(env).map(([k, v]) => `set ${k}=${v} &&`).join(" ");
50
+ const fullCmd = `cd /d "${cwd}" && ${envPrefix} ${command} ${args.join(" ")}`;
51
+
52
+ const proc = spawn("wt.exe", [
53
+ "-w", "0", "new-tab", "--title", title,
54
+ "--", "cmd.exe", "/k", fullCmd,
55
+ ], { detached: true, stdio: "ignore", shell: false });
56
+
57
+ proc.unref();
58
+ }
59
+
60
+ /** Launch in a plain new CMD window (fallback when wt.exe is absent). */
61
+ function launchInCmdWindow(cwd, command, args, env = {}) {
62
+ const envPrefix = Object.entries(env).map(([k, v]) => `set ${k}=${v} &&`).join(" ");
63
+ const fullCmd = `cd /d "${cwd}" && ${envPrefix} ${command} ${args.join(" ")}`;
64
+
65
+ const proc = spawn("cmd.exe", ["/c", "start", "cmd.exe", "/k", fullCmd], {
66
+ detached: true, stdio: "ignore", shell: false,
67
+ });
68
+ proc.unref();
69
+ }
70
+
71
+ /** Try Windows Terminal; return false if unavailable. */
72
+ function tryWindowsTerminal(title, cwd, command, args, env = {}) {
73
+ try { launchInWindowsTerminal(title, cwd, command, args, env); return true; }
74
+ catch { return false; }
75
+ }
76
+
77
+ /** Try common Unix/macOS terminals in priority order. */
78
+ function launchInUnixTerminal(title, cwd, command, args, env = {}) {
79
+ const envPrefix = Object.entries(env).map(([k, v]) => `${k}=${v}`).join(" ");
80
+ const fullCmd = `cd "${cwd}" && ${envPrefix} ${command} ${args.join(" ")}; exec $SHELL`;
81
+
82
+ const terminals = [
83
+ ["gnome-terminal", ["--title", title, "--", "bash", "-c", fullCmd]],
84
+ ["xterm", ["-title", title, "-e", `bash -c '${fullCmd}'`]],
85
+ ["osascript", ["-e", `tell application "Terminal" to do script "cd \\"${cwd}\\" && ${envPrefix} ${command} ${args.join(" ")}"`]],
86
+ ];
87
+
88
+ for (const [term, termArgs] of terminals) {
89
+ try {
90
+ spawn(term, termArgs, { detached: true, stdio: "ignore" }).unref();
91
+ return true;
92
+ } catch { /* try next */ }
93
+ }
94
+ return false;
95
+ }
96
+
97
+ // ─────────────────────────────────────────────────────────────
98
+ // Main export
99
+ // Signature: dev(chalk, args)
100
+ // No rl — dev doesn't need interactive prompts.
101
+ // ─────────────────────────────────────────────────────────────
102
+ export default async function dev(chalk, args = []) {
103
+ header(chalk, "dev");
104
+
105
+ // ── Guard: must be inside a valid project ─────────────────
106
+ if (!checkCompat(chalk, "dev")) return;
107
+
108
+ const root = process.cwd();
109
+
110
+ // ── TODO: define your service directories here ────────────
111
+ // const backendDir = path.join(root, "backend");
112
+ // const frontendDir = path.join(root, "frontend");
113
+ //
114
+ // Example existence check:
115
+ // if (!fs.existsSync(backendDir)) {
116
+ // fail(chalk, `backend/ not found in ${chalk.cyan(root)}`);
117
+ // return;
118
+ // }
119
+
120
+ // ── Allocate ports (remove if your plugin doesn't use them)
121
+ // const port = await getFreePort();
122
+ // info(chalk, `Allocated port ${port}`);
123
+
124
+ // ── TODO: launch your dev processes ──────────────────────
125
+ //
126
+ // Windows example:
127
+ // const launched = tryWindowsTerminal("My Dev Server", root, "npm", ["run", "dev"], {});
128
+ // if (!launched) launchInCmdWindow(root, "npm", ["run", "dev"], {});
129
+ //
130
+ // Unix example:
131
+ // const success = launchInUnixTerminal("My Dev Server", root, "npm", ["run", "dev"], {});
132
+ // if (!success) {
133
+ // fail(chalk, "Could not open terminal. Run manually:");
134
+ // console.log(chalk.gray(` cd "${root}" && npm run dev`));
135
+ // return;
136
+ // }
137
+
138
+ // ── Placeholder output — replace when you wire up processes
139
+ info(chalk, "Dev command placeholder — wire up your processes above.");
140
+ ok(chalk, "Nothing launched yet (template mode)");
141
+
142
+ console.log();
143
+ divider(chalk);
144
+ console.log(chalk.green(" ✔ Dev environment ready!"));
145
+ divider(chalk);
146
+ console.log();
147
+ }
@@ -0,0 +1,102 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/commands/install.js
3
+ //
4
+ // The "install" command installs dependencies for the project.
5
+ //
6
+ // This template shows:
7
+ // ✔ Running npm install / pip install per sub-folder
8
+ // ✔ Checking for package.json / requirements.txt before running
9
+ // ✔ Reporting success/failure per package manager
10
+ // ─────────────────────────────────────────────────────────────
11
+
12
+ import fs from "fs";
13
+ import path from "path";
14
+ import { execSync } from "child_process";
15
+ import { checkCompat } from "../config.js";
16
+ import { ok, fail, warn, info, step, header, divider } from "../logger.js";
17
+
18
+ // ── Directories to install deps for ───────────────────────────
19
+ // Each entry:
20
+ // dir → subfolder relative to project root
21
+ // type → "npm" | "pip"
22
+ //
23
+ // Add, remove, or reorder as your plugin needs.
24
+ const INSTALL_TARGETS = [
25
+ { dir: ".", type: "npm" }
26
+ // { dir: "frontend", type: "npm" },
27
+ // { dir: "backend", type: "pip" },
28
+ ];
29
+
30
+ // ─────────────────────────────────────────────────────────────
31
+ function exec(cmd, opts = {}) {
32
+ try {
33
+ execSync(cmd, { ...opts, stdio: "pipe" });
34
+ } catch (err) {
35
+ const out = [err.stdout?.toString(), err.stderr?.toString()].filter(Boolean).join("\n");
36
+ throw new Error(out || err.message);
37
+ }
38
+ }
39
+
40
+ // ─────────────────────────────────────────────────────────────
41
+ // Main export
42
+ // Signature: install(chalk, args)
43
+ // ─────────────────────────────────────────────────────────────
44
+ export default async function install(chalk, args = []) {
45
+ header(chalk, "install");
46
+
47
+ // ── Guard ─────────────────────────────────────────────────
48
+ if (!checkCompat(chalk, "install")) return;
49
+
50
+ const root = process.cwd();
51
+
52
+ if (INSTALL_TARGETS.length === 0) {
53
+ info(chalk, "No install targets defined yet.");
54
+ info(chalk, "Edit INSTALL_TARGETS in lib/commands/install.js to add your dirs.");
55
+ console.log();
56
+ return;
57
+ }
58
+
59
+ for (let i = 0; i < INSTALL_TARGETS.length; i++) {
60
+ const { dir, type } = INSTALL_TARGETS[i];
61
+ step(chalk, i + 1, INSTALL_TARGETS.length, `Installing ${dir}`);
62
+
63
+ const fullDir = path.join(root, dir);
64
+
65
+ if (!fs.existsSync(fullDir)) {
66
+ warn(chalk, `${dir}/ not found — skipping`);
67
+ continue;
68
+ }
69
+
70
+ try {
71
+ if (type === "npm") {
72
+ const pkgJson = path.join(fullDir, "package.json");
73
+ if (!fs.existsSync(pkgJson)) {
74
+ warn(chalk, `No package.json in ${dir}/ — skipping`);
75
+ continue;
76
+ }
77
+ info(chalk, `Running npm install in ${chalk.cyan(dir)}/...`);
78
+ exec("npm install", { cwd: fullDir });
79
+ ok(chalk, "npm dependencies installed");
80
+
81
+ } else if (type === "pip") {
82
+ const req = path.join(fullDir, "requirements.txt");
83
+ if (!fs.existsSync(req)) {
84
+ warn(chalk, `No requirements.txt in ${dir}/ — skipping`);
85
+ continue;
86
+ }
87
+ info(chalk, `Running pip install in ${chalk.cyan(dir)}/...`);
88
+ exec("pip install -r requirements.txt", { cwd: fullDir });
89
+ ok(chalk, "Python dependencies installed");
90
+ }
91
+ } catch (err) {
92
+ fail(chalk, `Failed to install ${dir}: ${err.message}`);
93
+ return;
94
+ }
95
+ }
96
+
97
+ console.log();
98
+ divider(chalk);
99
+ console.log(chalk.green(" ✔ All dependencies installed!"));
100
+ divider(chalk);
101
+ console.log();
102
+ }
package/lib/compat.js ADDED
@@ -0,0 +1,43 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/compat.js
3
+ //
4
+ // Single source of truth for version compatibility.
5
+ //
6
+ // HOW IT WORKS
7
+ // ─────────────
8
+ // • `pluginVersion` – the version of THIS plugin (bump on releases)
9
+ // • `COMPAT.global` – minimum project version required by ALL commands
10
+ // • `COMPAT.commands`– per-command overrides (takes priority over global)
11
+ //
12
+ // RANGE SYNTAX (no external deps)
13
+ // ────────────────────────────────
14
+ // ">=1.0.0" project created with plugin 1.0.0 or newer
15
+ // "<=2.0.0" project created with plugin 2.0.0 or older
16
+ // "1.0.0" exact version only
17
+ // ">=1.0.0||<=0.9.5" union (rare — use sparingly)
18
+ //
19
+ // WHEN TO BUMP
20
+ // ─────────────
21
+ // • You change a template repo structure that breaks an existing command
22
+ // → bump COMPAT.commands[thatCommand] to ">=<new-plugin-version>"
23
+ // • You change something that affects ALL commands (e.g. config layout)
24
+ // → bump COMPAT.global
25
+ // • You add a brand-new command that doesn't touch existing templates
26
+ // → no bump needed
27
+ // ─────────────────────────────────────────────────────────────
28
+
29
+ export const pluginVersion = "1.0.0";
30
+
31
+ export const COMPAT = {
32
+
33
+ // Every command: project must have been created with >= this version.
34
+ // Set to null to disable the global check.
35
+ global: ">=1.0.0",
36
+
37
+ // Per-command overrides.
38
+ // Only add an entry here when a command needs a tighter requirement
39
+ // than the global range.
40
+ commands: {
41
+ // "build": ">=1.0.0",
42
+ },
43
+ };
package/lib/config.js ADDED
@@ -0,0 +1,150 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/config.js
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { COMPAT, pluginVersion } from "./compat.js";
8
+
9
+ export const CONFIG_FILE = ".jdm-config.json";
10
+
11
+ // ── CHANGE THIS to match your plugin's namespace ──────────────
12
+ const PLUGIN_NAME = "plugin-template"; // ← CHANGE THIS
13
+
14
+ // ─────────────────────────────────────────────────────────────
15
+ // Version helpers
16
+ // ─────────────────────────────────────────────────────────────
17
+ function parseVer(v) {
18
+ return String(v).split(".").map(Number);
19
+ }
20
+
21
+ function cmpVer(a, b) {
22
+ const pa = parseVer(a);
23
+ const pb = parseVer(b);
24
+ for (let i = 0; i < 3; i++) {
25
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
26
+ if (diff !== 0) return diff < 0 ? -1 : 1;
27
+ }
28
+ return 0;
29
+ }
30
+
31
+ export function satisfies(version, range) {
32
+ if (!range) return true;
33
+ const rangeList = range.split("||").map(r => r.trim());
34
+ return rangeList.some(r => {
35
+ if (r.startsWith(">=")) return cmpVer(version, r.slice(2)) >= 0;
36
+ if (r.startsWith("<=")) return cmpVer(version, r.slice(2)) <= 0;
37
+ if (r.startsWith(">")) return cmpVer(version, r.slice(1)) > 0;
38
+ if (r.startsWith("<")) return cmpVer(version, r.slice(1)) < 0;
39
+ return cmpVer(version, r) === 0; // exact match
40
+ });
41
+ }
42
+
43
+ // ─────────────────────────────────────────────────────────────
44
+ // Config read / write
45
+ // ─────────────────────────────────────────────────────────────
46
+ export function configExists(root = process.cwd()) {
47
+ return fs.existsSync(path.join(root, CONFIG_FILE));
48
+ }
49
+
50
+ /**
51
+ * Read the full .jdm-config.json and return only this plugin's slice.
52
+ * Returns null → file is missing or unparseable.
53
+ * Returns {} → file exists but has no entry for this plugin yet.
54
+ */
55
+ export function readConfig(root = process.cwd()) {
56
+ const p = path.join(root, CONFIG_FILE);
57
+ if (!fs.existsSync(p)) return null;
58
+ try {
59
+ const full = JSON.parse(fs.readFileSync(p, "utf8"));
60
+ return full[PLUGIN_NAME] ?? {};
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Merge this plugin's data into .jdm-config.json under its own key.
68
+ * Other plugins' keys are left untouched.
69
+ *
70
+ * Resulting shape:
71
+ * {
72
+ * "plugin-template": { pluginVersion, createdAt, ...extra },
73
+ * "other-plugin": { ... } <- untouched
74
+ * }
75
+ */
76
+ export function writeConfig(root = process.cwd(), extra = {}) {
77
+ const p = path.join(root, CONFIG_FILE);
78
+
79
+ // Preserve any existing keys from other plugins
80
+ let full = {};
81
+ if (fs.existsSync(p)) {
82
+ try { full = JSON.parse(fs.readFileSync(p, "utf8")); } catch { /* start fresh */ }
83
+ }
84
+
85
+ const pluginData = {
86
+ pluginVersion,
87
+ createdAt: new Date().toISOString(),
88
+ ...extra,
89
+ };
90
+
91
+ full[PLUGIN_NAME] = pluginData;
92
+ fs.writeFileSync(p, JSON.stringify(full, null, 2) + "\n", "utf8");
93
+ return pluginData;
94
+ }
95
+
96
+ // ─────────────────────────────────────────────────────────────
97
+ // checkCompat()
98
+ //
99
+ // Call at the top of any command that requires the user to be
100
+ // inside a project scaffolded by this plugin.
101
+ //
102
+ // Returns true → safe to proceed
103
+ // Returns false → caller should return early
104
+ // ─────────────────────────────────────────────────────────────
105
+ export function checkCompat(chalk, command) {
106
+ const root = process.cwd();
107
+ const cfg = readConfig(root);
108
+
109
+ // cfg === null → file missing entirely
110
+ // cfg === {} → file exists but has no entry for this plugin yet
111
+ if (cfg === null || !cfg.pluginVersion) {
112
+ console.log();
113
+ console.log(chalk.yellow(" ⚠ No .jdm-config.json entry found for this plugin."));
114
+ console.log(chalk.gray(` This may not be a jdm-${PLUGIN_NAME} project,`));
115
+ console.log(chalk.gray(" or it was created before config tracking was introduced."));
116
+ const globalRange = COMPAT.global ?? null;
117
+ if (globalRange) {
118
+ console.log(chalk.gray(` Expected a project created with plugin ${chalk.white(globalRange)}.`));
119
+ }
120
+ console.log(chalk.gray(" Proceeding anyway — things may not work as expected.\n"));
121
+ return true;
122
+ }
123
+
124
+ // ── Version range check ───────────────────────────────────
125
+ const projectVer = cfg.pluginVersion;
126
+ const range = COMPAT.commands?.[command] ?? COMPAT.global ?? null;
127
+ if (!range) return true;
128
+
129
+ if (!satisfies(projectVer, range)) {
130
+ const cmdLabel = COMPAT.commands?.[command] ? `"${command}"` : "this plugin";
131
+ console.log();
132
+ console.log(chalk.red(` ✖ Compatibility error for command: ${chalk.bold(command)}`));
133
+ console.log(
134
+ chalk.gray(" Project was created with plugin version ") +
135
+ chalk.cyan(projectVer) +
136
+ chalk.gray(",")
137
+ );
138
+ console.log(
139
+ chalk.gray(` but ${cmdLabel} requires `) +
140
+ chalk.white(range) +
141
+ chalk.gray(".")
142
+ );
143
+ console.log();
144
+ console.log(chalk.yellow(" Tip: ") + chalk.gray("Re-scaffold with ") + chalk.white(`jdm-cli ${PLUGIN_NAME} create`));
145
+ console.log(chalk.gray(" or update the plugin to a version that supports your project.\n"));
146
+ return false;
147
+ }
148
+
149
+ return true;
150
+ }
package/lib/index.js ADDED
@@ -0,0 +1,107 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/index.js
3
+ //
4
+ // Main entry point for the plugin.
5
+ // jdm-cli imports this file and calls run(command, args, chalk, rl).
6
+ //
7
+ // Required exports:
8
+ // namespace (string) — the CLI prefix: jdm <namespace> <command>
9
+ // commands (object) — map of command name → handler function
10
+ // run (fn) — dispatcher called by jdm-cli
11
+ // showDesign (fn) — prints the help screen
12
+ //
13
+ // To add a new command:
14
+ // 1. Create lib/commands/my-command.js with a default export
15
+ // 2. Import it here
16
+ // 3. Add it to the `commands` map
17
+ // 4. Add it to showDesign()
18
+ // 5. Add it to the jdmPlugin.commands array in package.json
19
+ // ─────────────────────────────────────────────────────────────
20
+
21
+ import create from "./commands/create.js";
22
+ import dev from "./commands/dev.js";
23
+ import build from "./commands/build.js";
24
+ import clean from "./commands/clean.js";
25
+ import install from "./commands/install.js";
26
+
27
+ // ── Namespace ─────────────────────────────────────────────────
28
+ // This is the prefix used in the CLI: jdm <namespace> <command>
29
+ // Change this to match your plugin's jdmPlugin.namespace in package.json
30
+ export const namespace = "plugin-template"; // ← CHANGE THIS
31
+
32
+ // ── Command map ───────────────────────────────────────────────
33
+ // Keys are the exact command strings the user types.
34
+ // Values are the imported handler functions.
35
+ //
36
+ // Commands that need interactive prompts receive (chalk, rl, args).
37
+ // Commands that don't need prompts receive (chalk, args).
38
+ // See the run() dispatcher below for how this is handled.
39
+ export const commands = {
40
+ create,
41
+ dev,
42
+ build,
43
+ clean,
44
+ install,
45
+
46
+ // ── Add your own commands here ────────────────────────────
47
+ // "my-command": myCommand,
48
+ };
49
+
50
+ // ── Commands that need the readline interface (rl) ────────────
51
+ // List any command names that call ask(rl, ...) for user input.
52
+ const INTERACTIVE_COMMANDS = ["create"];
53
+
54
+ // ─────────────────────────────────────────────────────────────
55
+ // run() — called by jdm-cli for every invocation
56
+ //
57
+ // command (string) — e.g. "create", "dev", "build"
58
+ // args (string[]) — remaining CLI args / flags
59
+ // chalk (object) — chalk instance from jdm-cli
60
+ // rl (object) — readline interface from jdm-cli
61
+ // ─────────────────────────────────────────────────────────────
62
+ export async function run(command, args, chalk, rl) {
63
+
64
+ // ── Help shortcut ─────────────────────────────────────────
65
+ if (command === "help" || command === "--help" || command === "-h" || !command) {
66
+ return showDesign(chalk);
67
+ }
68
+
69
+ // ── Lookup ────────────────────────────────────────────────
70
+ const fn = commands[command];
71
+ if (!fn) {
72
+ console.log(chalk.red(`\n ✖ Unknown command: "${command}"`));
73
+ console.log(chalk.gray(` Available: ${Object.keys(commands).join(", ")}`));
74
+ return;
75
+ }
76
+
77
+ // ── Dispatch ──────────────────────────────────────────────
78
+ if (INTERACTIVE_COMMANDS.includes(command)) {
79
+ return fn(chalk, rl, args);
80
+ }
81
+ return fn(chalk, args);
82
+ }
83
+
84
+ // ─────────────────────────────────────────────────────────────
85
+ // showDesign() — the help / overview screen
86
+ //
87
+ // Customize this to describe your plugin's commands.
88
+ // ─────────────────────────────────────────────────────────────
89
+ export async function showDesign(chalk) {
90
+ console.log(chalk.cyan(`\n ⚡ ${namespace} Plugin`));
91
+ // ← Replace the tagline with your plugin's description
92
+ console.log(chalk.gray(" Your plugin tagline goes here."));
93
+ console.log(chalk.gray(" Available commands:\n"));
94
+
95
+ // ── List your commands here ───────────────────────────────
96
+ // Format: command name (padded) + short description
97
+ console.log(` ${chalk.green("create")} ${chalk.dim("Scaffold a new project")}`);
98
+ console.log(` ${chalk.green("dev")} ${chalk.dim("Start development environment")}`);
99
+ console.log(` ${chalk.green("build")} ${chalk.dim("Compile / package the project")}`);
100
+ console.log(` ${chalk.green("clean")} ${chalk.dim("Remove build artifacts")}`);
101
+ console.log(` ${chalk.green("install")} ${chalk.dim("Install dependencies")}`);
102
+
103
+ // ── Add your own commands to the list above ───────────────
104
+ // console.log(` ${chalk.green("my-command")} ${chalk.dim("Does something useful")}`);
105
+
106
+ console.log();
107
+ }
package/lib/logger.js ADDED
@@ -0,0 +1,45 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // jdm-plugin-template — lib/logger.js
3
+ //
4
+ // Shared logging helpers used across all commands.
5
+ // Import what you need:
6
+ // import { ok, fail, info, warn, step, header } from "../logger.js";
7
+ //
8
+ // All functions take (chalk, msg) so chalk stays injectable
9
+ // and the plugin doesn't need to manage a global chalk ref.
10
+ // ─────────────────────────────────────────────────────────────
11
+
12
+ // ── Pretty-print helpers ──────────────────────────────────────
13
+ export function ok(chalk, msg) { console.log(chalk.green(" ✔ ") + msg); }
14
+ export function fail(chalk, msg) { console.log(chalk.red(" ✖ ") + msg); }
15
+ export function warn(chalk, msg) { console.log(chalk.yellow(" ⚠ ") + msg); }
16
+ export function info(chalk, msg) { console.log(chalk.gray(" · ") + msg); }
17
+
18
+ // ── Step counter line ─────────────────────────────────────────
19
+ // step(chalk, 1, 3, "Doing the thing") → [1/3] Doing the thing
20
+ export function step(chalk, n, total, label) {
21
+ console.log();
22
+ console.log(chalk.cyan(` [${n}/${total}]`) + " " + chalk.bold(label));
23
+ }
24
+
25
+ // ── Command header ────────────────────────────────────────────
26
+ // header(chalk, "create") → jdm / your-namespace / create
27
+ //
28
+ // Customize the namespace string to match your plugin.
29
+ export function header(chalk, command = "") {
30
+ const ns = "plugin-template"; // ← replace with your namespace
31
+ console.log();
32
+ console.log(
33
+ chalk.cyan(" jdm") +
34
+ chalk.gray(" / ") +
35
+ chalk.white(ns) +
36
+ (command ? chalk.gray(" / ") + chalk.bold(command) : "")
37
+ );
38
+ console.log(chalk.gray(" ─────────────────────────────────────"));
39
+ console.log();
40
+ }
41
+
42
+ // ── Section divider ───────────────────────────────────────────
43
+ export function divider(chalk) {
44
+ console.log(chalk.gray(" ─────────────────────────────────────"));
45
+ }
package/package.json ADDED
@@ -0,0 +1,104 @@
1
+ {
2
+ "name": "jdm-plugin-template",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "A starter template for building jdm-cli plugins",
6
+ "main": "./lib/index.js",
7
+ "scripts": {
8
+ "test": "echo \"No tests yet\""
9
+ },
10
+ "dependencies": {
11
+ "chalk": "^5.3.0",
12
+ "ora": "^9.4.0",
13
+ "string-width": "^8.2.0",
14
+ "strip-ansi": "^7.2.0"
15
+ },
16
+ "keywords": [
17
+ "jdm",
18
+ "plugin",
19
+ "template"
20
+ ],
21
+ "author": "JDM-Github",
22
+ "license": "MIT",
23
+ "jdmPlugin": {
24
+ "namespace": "plugin-template",
25
+ "description": "Your plugin description goes here",
26
+ "commands": [
27
+ {
28
+ "name": "create",
29
+ "description": "Scaffold a new project",
30
+ "fields": [
31
+ {
32
+ "key": "name",
33
+ "label": "Project Name",
34
+ "flag": "--name",
35
+ "type": "text",
36
+ "placeholder": "my-project",
37
+ "required": true
38
+ },
39
+ {
40
+ "key": "install",
41
+ "label": "Install Dependencies",
42
+ "flag": "--install",
43
+ "type": "boolean",
44
+ "default": false
45
+ }
46
+ ]
47
+ },
48
+ {
49
+ "name": "dev",
50
+ "description": "Start development environment",
51
+ "fields": []
52
+ },
53
+ {
54
+ "name": "build",
55
+ "description": "Compile / package the project",
56
+ "fields": [
57
+ {
58
+ "key": "frontend",
59
+ "label": "Build Frontend",
60
+ "flag": "--frontend",
61
+ "type": "boolean",
62
+ "default": false,
63
+ "description": "Build only the frontend"
64
+ },
65
+ {
66
+ "key": "backend",
67
+ "label": "Build Backend",
68
+ "flag": "--backend",
69
+ "type": "boolean",
70
+ "default": false,
71
+ "description": "Build only the backend"
72
+ },
73
+ {
74
+ "key": "full",
75
+ "label": "Full Build",
76
+ "flag": "--full",
77
+ "type": "boolean",
78
+ "default": false,
79
+ "description": "Build everything (frontend + backend)"
80
+ }
81
+ ]
82
+ },
83
+ {
84
+ "name": "clean",
85
+ "description": "Remove build artifacts",
86
+ "fields": [
87
+ {
88
+ "key": "dry",
89
+ "label": "Dry Run",
90
+ "flag": "--dry",
91
+ "type": "boolean",
92
+ "default": false,
93
+ "description": "Preview what would be deleted without deleting"
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ "name": "install",
99
+ "description": "Install dependencies",
100
+ "fields": []
101
+ }
102
+ ]
103
+ }
104
+ }