plop-pack-git-commit 0.1.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 Borja López Felipe
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,162 @@
1
+ # plop-pack-git-commit
2
+
3
+ PlopJS action pack that stages changes, creates a git commit, and pushes to `origin`.
4
+
5
+ Inspired by [plop-pack-git-init](https://github.com/crutchcorn/plop-pack-git-init), but focused on committing existing or generated files in an already initialized repository.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add plop-pack-git-commit
11
+ # or
12
+ npm i plop-pack-git-commit
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```js
18
+ module.exports = function (plop) {
19
+ plop.load("plop-pack-git-commit");
20
+
21
+ plop.setGenerator("example", {
22
+ prompts: [],
23
+ actions: [
24
+ {
25
+ type: "add",
26
+ path: "notes/{{name}}.md",
27
+ template: "# {{name}}\n",
28
+ },
29
+ {
30
+ type: "gitCommit",
31
+ path: process.cwd(),
32
+ message: "docs: add {{name}} note",
33
+ files: "notes/{{name}}.md",
34
+ },
35
+ ],
36
+ });
37
+ };
38
+ ```
39
+
40
+ ### Commit specific file(s)
41
+
42
+ ```js
43
+ {
44
+ type: "gitCommit",
45
+ path: process.cwd(),
46
+ message: "docs: add contact note",
47
+ files: "notes/contacto.md",
48
+ }
49
+ ```
50
+
51
+ `files` accepts a string or an array of strings.
52
+
53
+ ### Commit everything pending
54
+
55
+ ```js
56
+ {
57
+ type: "gitCommit",
58
+ path: process.cwd(),
59
+ message: "chore: sync generated files",
60
+ all: true,
61
+ }
62
+ ```
63
+
64
+ This runs `git add -A` before committing.
65
+
66
+ ## Options
67
+
68
+ | Option | Type | Default | Description |
69
+ |--------|------|---------|-------------|
70
+ | `path` | `string` | `process.cwd()` | Repository path |
71
+ | `message` | `string` | required | Commit message |
72
+ | `files` | `string \| string[]` | — | Stage only these paths |
73
+ | `all` | `boolean` | — | Stage all changes with `git add -A` |
74
+ | `verbose` | `boolean` | `false` | Stream git output to the terminal |
75
+ | `skipEmpty` | `boolean` | `true` | Resolve instead of failing when there is nothing to commit |
76
+
77
+ Provide either `files` or `all: true`. They are mutually exclusive.
78
+
79
+ ## Push behavior
80
+
81
+ After a successful commit, the action always runs:
82
+
83
+ ```bash
84
+ git push origin HEAD
85
+ ```
86
+
87
+ - If there was nothing to commit (`skipEmpty`), push is skipped.
88
+ - If `origin` is missing or push fails, the action resolves with a warning instead of failing the generator. The commit remains local.
89
+
90
+ ## Schema export
91
+
92
+ You can validate action configs outside Plop:
93
+
94
+ ```ts
95
+ import { gitCommitConfigSchema } from "plop-pack-git-commit";
96
+
97
+ const result = gitCommitConfigSchema.safeParse({
98
+ path: process.cwd(),
99
+ message: "feat: add generator output",
100
+ all: true,
101
+ });
102
+ ```
103
+
104
+ ## Requirements
105
+
106
+ - `git` available in `PATH`
107
+ - Git user identity configured (`user.name`, `user.email`)
108
+ - Remote `origin` configured when you expect push to succeed
109
+
110
+ ## Development
111
+
112
+ ```bash
113
+ pnpm install
114
+ pnpm check # lint + test + build
115
+ pnpm lint
116
+ pnpm test
117
+ pnpm build
118
+ ```
119
+
120
+ ## GitHub Actions
121
+
122
+ | Workflow | Trigger | Purpose |
123
+ |----------|---------|---------|
124
+ | `CI` | push/PR to `main` | lint, test, build |
125
+ | `Security audit` | push/PR + weekly | `pnpm audit` fails on high/critical |
126
+ | `Dependency review` | pull requests | blocks PRs that add vulnerable deps |
127
+ | `CodeQL` | push/PR + weekly | static analysis for TypeScript/JavaScript |
128
+ | `Release` | GitHub Release published | verify tag, `pnpm check`, publish to npm |
129
+
130
+ Dependabot opens weekly PRs to update dependencies.
131
+
132
+ ### Security releases (automated)
133
+
134
+ When a **Dependabot security PR** is merged to `main`:
135
+
136
+ 1. `Security release prepare` checks:
137
+ - PR author is `dependabot[bot]`
138
+ - PR body/labels reference security advisories (GHSA/CVE)
139
+ - `pnpm audit` shows fewer vulnerabilities than before the merge
140
+ 2. If all pass, it opens a review PR (`security-release/x.y.z`) that bumps the **patch** version (`z`) and prepends a **Security** section to `CHANGELOG.md` (packages, versions, advisories).
141
+ 3. If audit does not improve, no release PR is created.
142
+ 4. When you merge the security release PR, `Security release publish` tags `vx.y.z`, creates a GitHub Release titled `Security release x.y.z`, and publishes to npm.
143
+
144
+ Manual feature releases still use the `Release` workflow (GitHub Release UI). Bot-authored security releases use the dedicated publish workflow to avoid duplicate npm publishes.
145
+
146
+ ### Releasing to npm (manual)
147
+
148
+ 1. Bump `version` in `package.json` (e.g. `0.1.1`).
149
+ 2. Commit, push to `main`, and create a GitHub Release with tag `v0.1.1` (must match `package.json`).
150
+ 3. The `Release` workflow runs `pnpm check` and publishes with [npm provenance](https://docs.npmjs.com/generating-provenance-statements).
151
+
152
+ Repository secret required:
153
+
154
+ | Secret | Purpose |
155
+ |--------|---------|
156
+ | `NPM_TOKEN` | npm automation token with publish access to this package |
157
+
158
+ Enable **Dependabot alerts** and **Code scanning** under repository Settings → Code security.
159
+
160
+ ## License
161
+
162
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ default: () => registerGitCommitPack,
34
+ gitCommitAction: () => gitCommitAction,
35
+ gitCommitConfigSchema: () => gitCommitConfigSchema
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/git-commit-action.ts
40
+ var import_promises = require("fs/promises");
41
+ var import_node_path = __toESM(require("path"), 1);
42
+
43
+ // src/lib/run-git.ts
44
+ var import_node_child_process = require("child_process");
45
+ var didSucceed = (code) => code === 0;
46
+ function runGit(args, options) {
47
+ const { cwd, verbose = false } = options;
48
+ return new Promise((resolve, reject) => {
49
+ const child = (0, import_node_child_process.spawn)("git", args, {
50
+ cwd,
51
+ stdio: verbose ? "inherit" : "pipe"
52
+ });
53
+ if (verbose) {
54
+ child.on("close", (code) => {
55
+ if (didSucceed(code)) {
56
+ resolve({ stdout: "", stderr: "" });
57
+ return;
58
+ }
59
+ reject(new Error(`git ${args.join(" ")} exited with code ${code ?? "unknown"}`));
60
+ });
61
+ child.on("error", reject);
62
+ return;
63
+ }
64
+ let stdout = "";
65
+ let stderr = "";
66
+ child.stdout?.on("data", (chunk) => {
67
+ stdout += chunk.toString();
68
+ });
69
+ child.stderr?.on("data", (chunk) => {
70
+ stderr += chunk.toString();
71
+ });
72
+ child.on("close", (code) => {
73
+ if (didSucceed(code)) {
74
+ resolve({ stdout, stderr });
75
+ return;
76
+ }
77
+ const detail = stderr.trim() || stdout.trim();
78
+ reject(
79
+ new Error(
80
+ detail ? `git ${args.join(" ")} failed: ${detail}` : `git ${args.join(" ")} exited with code ${code ?? "unknown"}`
81
+ )
82
+ );
83
+ });
84
+ child.on("error", reject);
85
+ });
86
+ }
87
+
88
+ // src/lib/git-push.ts
89
+ async function hasOriginRemote(repoPath, verbose) {
90
+ try {
91
+ await runGit(["remote", "get-url", "origin"], { cwd: repoPath, verbose });
92
+ return true;
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+ async function pushToOrigin(repoPath, verbose = false) {
98
+ if (!await hasOriginRemote(repoPath, verbose)) {
99
+ return {
100
+ ok: false,
101
+ warning: "Committed; push skipped: remote origin is not configured"
102
+ };
103
+ }
104
+ try {
105
+ await runGit(["push", "origin", "HEAD"], { cwd: repoPath, verbose });
106
+ return { ok: true };
107
+ } catch (error) {
108
+ const message = error instanceof Error ? error.message : String(error);
109
+ return {
110
+ ok: false,
111
+ warning: `Committed; push failed: ${message}`
112
+ };
113
+ }
114
+ }
115
+
116
+ // src/schemas/git-commit-config.ts
117
+ var import_zod = require("zod");
118
+ var gitCommitConfigSchema = import_zod.z.object({
119
+ path: import_zod.z.string().min(1),
120
+ message: import_zod.z.string().trim().min(1),
121
+ files: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.array(import_zod.z.string().min(1)).min(1)]).optional(),
122
+ all: import_zod.z.boolean().optional(),
123
+ verbose: import_zod.z.boolean().default(false),
124
+ skipEmpty: import_zod.z.boolean().default(true)
125
+ }).refine((data) => data.files !== void 0 !== (data.all === true), {
126
+ message: "Provide either files or all: true, not both or neither"
127
+ });
128
+
129
+ // src/git-commit-action.ts
130
+ async function isGitRepository(repoPath) {
131
+ try {
132
+ await (0, import_promises.access)(import_node_path.default.join(repoPath, ".git"));
133
+ return true;
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+ function formatZodError(error) {
139
+ return error.issues.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`).join("; ");
140
+ }
141
+ function normalizeFiles(files) {
142
+ return Array.isArray(files) ? files : [files];
143
+ }
144
+ async function hasChangesToStage(repoPath, verbose) {
145
+ const { stdout } = await runGit(["status", "--porcelain"], {
146
+ cwd: repoPath,
147
+ verbose
148
+ });
149
+ return stdout.trim().length > 0;
150
+ }
151
+ async function stageFiles(repoPath, config, verbose) {
152
+ if (config.files !== void 0) {
153
+ await runGit(["add", "--", ...normalizeFiles(config.files)], {
154
+ cwd: repoPath,
155
+ verbose
156
+ });
157
+ return;
158
+ }
159
+ if (config.all) {
160
+ await runGit(["add", "-A"], { cwd: repoPath, verbose });
161
+ }
162
+ }
163
+ async function hasStagedDiff(repoPath, verbose) {
164
+ try {
165
+ await runGit(["diff", "--cached", "--quiet"], { cwd: repoPath, verbose });
166
+ return false;
167
+ } catch (error) {
168
+ if (error instanceof Error && error.message.includes("exited with code 1")) {
169
+ return true;
170
+ }
171
+ throw error;
172
+ }
173
+ }
174
+ async function gitCommitAction(_answers, config) {
175
+ const parsed = gitCommitConfigSchema.safeParse({
176
+ path: config.path ?? process.cwd(),
177
+ message: config.message,
178
+ files: config.files,
179
+ all: config.all,
180
+ verbose: config.verbose ?? false,
181
+ skipEmpty: config.skipEmpty ?? true
182
+ });
183
+ if (!parsed.success) {
184
+ throw new Error(`Invalid gitCommit config: ${formatZodError(parsed.error)}`);
185
+ }
186
+ const { path: repoPath, message, verbose, skipEmpty, files, all } = parsed.data;
187
+ if (!await isGitRepository(repoPath)) {
188
+ throw new Error(`Not a git repository: ${repoPath}`);
189
+ }
190
+ const hasWorkingTreeChanges = await hasChangesToStage(repoPath, verbose);
191
+ if (!hasWorkingTreeChanges && skipEmpty) {
192
+ return "Nothing to commit";
193
+ }
194
+ if (!hasWorkingTreeChanges && !skipEmpty) {
195
+ throw new Error("Nothing to commit");
196
+ }
197
+ await stageFiles(repoPath, { files, all }, verbose);
198
+ const staged = await hasStagedDiff(repoPath, verbose);
199
+ if (!staged && skipEmpty) {
200
+ return "Nothing to commit";
201
+ }
202
+ if (!staged && !skipEmpty) {
203
+ throw new Error("Nothing to commit after staging");
204
+ }
205
+ await runGit(["commit", "-m", message], { cwd: repoPath, verbose });
206
+ const pushResult = await pushToOrigin(repoPath, verbose);
207
+ if (pushResult.ok) {
208
+ return `Committed and pushed to origin: ${message}`;
209
+ }
210
+ if (verbose) {
211
+ console.warn(pushResult.warning);
212
+ }
213
+ return pushResult.warning;
214
+ }
215
+
216
+ // src/index.ts
217
+ function registerGitCommitPack(plop) {
218
+ plop.setDefaultInclude({ actionTypes: true });
219
+ plop.setActionType("gitCommit", gitCommitAction);
220
+ }
221
+ // Annotate the CommonJS export names for ESM import in node:
222
+ 0 && (module.exports = {
223
+ gitCommitAction,
224
+ gitCommitConfigSchema
225
+ });
226
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/git-commit-action.ts","../src/lib/run-git.ts","../src/lib/git-push.ts","../src/schemas/git-commit-config.ts"],"sourcesContent":["import type { CustomActionFunction, NodePlopAPI } from \"node-plop\";\nimport { gitCommitAction } from \"./git-commit-action.js\";\nimport {\n type GitCommitActionConfig,\n type GitCommitConfig,\n gitCommitConfigSchema,\n} from \"./schemas/git-commit-config.js\";\n\nexport { gitCommitAction, gitCommitConfigSchema };\nexport type { GitCommitActionConfig, GitCommitConfig };\n\nexport default function registerGitCommitPack(plop: NodePlopAPI): void {\n plop.setDefaultInclude({ actionTypes: true });\n plop.setActionType(\"gitCommit\", gitCommitAction as CustomActionFunction);\n}\n","import { access } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pushToOrigin } from \"./lib/git-push.js\";\nimport { runGit } from \"./lib/run-git.js\";\nimport { type GitCommitActionConfig, gitCommitConfigSchema } from \"./schemas/git-commit-config.js\";\n\nasync function isGitRepository(repoPath: string): Promise<boolean> {\n try {\n await access(path.join(repoPath, \".git\"));\n return true;\n } catch {\n return false;\n }\n}\n\nfunction formatZodError(error: {\n issues: Array<{ path: Array<string | number>; message: string }>;\n}) {\n return error.issues\n .map((issue) => `${issue.path.join(\".\") || \"config\"}: ${issue.message}`)\n .join(\"; \");\n}\n\nfunction normalizeFiles(files: string | string[]): string[] {\n return Array.isArray(files) ? files : [files];\n}\n\nasync function hasChangesToStage(repoPath: string, verbose: boolean): Promise<boolean> {\n const { stdout } = await runGit([\"status\", \"--porcelain\"], {\n cwd: repoPath,\n verbose,\n });\n return stdout.trim().length > 0;\n}\n\nasync function stageFiles(\n repoPath: string,\n config: { files?: string | string[]; all?: boolean },\n verbose: boolean,\n): Promise<void> {\n if (config.files !== undefined) {\n await runGit([\"add\", \"--\", ...normalizeFiles(config.files)], {\n cwd: repoPath,\n verbose,\n });\n return;\n }\n\n if (config.all) {\n await runGit([\"add\", \"-A\"], { cwd: repoPath, verbose });\n }\n}\n\nasync function hasStagedDiff(repoPath: string, verbose: boolean): Promise<boolean> {\n try {\n await runGit([\"diff\", \"--cached\", \"--quiet\"], { cwd: repoPath, verbose });\n return false;\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"exited with code 1\")) {\n return true;\n }\n throw error;\n }\n}\n\nexport async function gitCommitAction(\n _answers: unknown,\n config: GitCommitActionConfig & Record<string, unknown>,\n): Promise<string> {\n const parsed = gitCommitConfigSchema.safeParse({\n path: config.path ?? process.cwd(),\n message: config.message,\n files: config.files,\n all: config.all,\n verbose: config.verbose ?? false,\n skipEmpty: config.skipEmpty ?? true,\n });\n\n if (!parsed.success) {\n throw new Error(`Invalid gitCommit config: ${formatZodError(parsed.error)}`);\n }\n\n const { path: repoPath, message, verbose, skipEmpty, files, all } = parsed.data;\n\n if (!(await isGitRepository(repoPath))) {\n throw new Error(`Not a git repository: ${repoPath}`);\n }\n\n const hasWorkingTreeChanges = await hasChangesToStage(repoPath, verbose);\n\n if (!hasWorkingTreeChanges && skipEmpty) {\n return \"Nothing to commit\";\n }\n\n if (!hasWorkingTreeChanges && !skipEmpty) {\n throw new Error(\"Nothing to commit\");\n }\n\n await stageFiles(repoPath, { files, all }, verbose);\n\n const staged = await hasStagedDiff(repoPath, verbose);\n\n if (!staged && skipEmpty) {\n return \"Nothing to commit\";\n }\n\n if (!staged && !skipEmpty) {\n throw new Error(\"Nothing to commit after staging\");\n }\n\n await runGit([\"commit\", \"-m\", message], { cwd: repoPath, verbose });\n\n const pushResult = await pushToOrigin(repoPath, verbose);\n\n if (pushResult.ok) {\n return `Committed and pushed to origin: ${message}`;\n }\n\n if (verbose) {\n console.warn(pushResult.warning);\n }\n\n return pushResult.warning;\n}\n","import { spawn } from \"node:child_process\";\n\nexport type RunGitOptions = {\n cwd: string;\n verbose?: boolean;\n};\n\nexport type RunGitResult = {\n stdout: string;\n stderr: string;\n};\n\nconst didSucceed = (code: number | null): boolean => code === 0;\n\nexport function runGit(args: string[], options: RunGitOptions): Promise<RunGitResult> {\n const { cwd, verbose = false } = options;\n\n return new Promise((resolve, reject) => {\n const child = spawn(\"git\", args, {\n cwd,\n stdio: verbose ? \"inherit\" : \"pipe\",\n });\n\n if (verbose) {\n child.on(\"close\", (code) => {\n if (didSucceed(code)) {\n resolve({ stdout: \"\", stderr: \"\" });\n return;\n }\n\n reject(new Error(`git ${args.join(\" \")} exited with code ${code ?? \"unknown\"}`));\n });\n child.on(\"error\", reject);\n return;\n }\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout?.on(\"data\", (chunk: Buffer | string) => {\n stdout += chunk.toString();\n });\n\n child.stderr?.on(\"data\", (chunk: Buffer | string) => {\n stderr += chunk.toString();\n });\n\n child.on(\"close\", (code) => {\n if (didSucceed(code)) {\n resolve({ stdout, stderr });\n return;\n }\n\n const detail = stderr.trim() || stdout.trim();\n reject(\n new Error(\n detail\n ? `git ${args.join(\" \")} failed: ${detail}`\n : `git ${args.join(\" \")} exited with code ${code ?? \"unknown\"}`,\n ),\n );\n });\n\n child.on(\"error\", reject);\n });\n}\n","import { runGit } from \"./run-git.js\";\n\nexport type PushResult = { ok: true } | { ok: false; warning: string };\n\nasync function hasOriginRemote(repoPath: string, verbose: boolean): Promise<boolean> {\n try {\n await runGit([\"remote\", \"get-url\", \"origin\"], { cwd: repoPath, verbose });\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function pushToOrigin(repoPath: string, verbose = false): Promise<PushResult> {\n if (!(await hasOriginRemote(repoPath, verbose))) {\n return {\n ok: false,\n warning: \"Committed; push skipped: remote origin is not configured\",\n };\n }\n\n try {\n await runGit([\"push\", \"origin\", \"HEAD\"], { cwd: repoPath, verbose });\n return { ok: true };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n ok: false,\n warning: `Committed; push failed: ${message}`,\n };\n }\n}\n","import { z } from \"zod\";\n\nexport const gitCommitConfigSchema = z\n .object({\n path: z.string().min(1),\n message: z.string().trim().min(1),\n files: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]).optional(),\n all: z.boolean().optional(),\n verbose: z.boolean().default(false),\n skipEmpty: z.boolean().default(true),\n })\n .refine((data) => (data.files !== undefined) !== (data.all === true), {\n message: \"Provide either files or all: true, not both or neither\",\n });\n\nexport type GitCommitConfig = z.infer<typeof gitCommitConfigSchema>;\n\nexport type GitCommitActionConfig = {\n path?: string;\n message?: string;\n files?: string | string[];\n all?: boolean;\n verbose?: boolean;\n skipEmpty?: boolean;\n};\n"],"mappings":"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","names":["path"]}
@@ -0,0 +1,54 @@
1
+ import { NodePlopAPI } from 'node-plop';
2
+ import { z } from 'zod';
3
+
4
+ declare const gitCommitConfigSchema: z.ZodEffects<z.ZodObject<{
5
+ path: z.ZodString;
6
+ message: z.ZodString;
7
+ files: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
8
+ all: z.ZodOptional<z.ZodBoolean>;
9
+ verbose: z.ZodDefault<z.ZodBoolean>;
10
+ skipEmpty: z.ZodDefault<z.ZodBoolean>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ verbose: boolean;
13
+ path: string;
14
+ message: string;
15
+ skipEmpty: boolean;
16
+ files?: string | string[] | undefined;
17
+ all?: boolean | undefined;
18
+ }, {
19
+ path: string;
20
+ message: string;
21
+ verbose?: boolean | undefined;
22
+ files?: string | string[] | undefined;
23
+ all?: boolean | undefined;
24
+ skipEmpty?: boolean | undefined;
25
+ }>, {
26
+ verbose: boolean;
27
+ path: string;
28
+ message: string;
29
+ skipEmpty: boolean;
30
+ files?: string | string[] | undefined;
31
+ all?: boolean | undefined;
32
+ }, {
33
+ path: string;
34
+ message: string;
35
+ verbose?: boolean | undefined;
36
+ files?: string | string[] | undefined;
37
+ all?: boolean | undefined;
38
+ skipEmpty?: boolean | undefined;
39
+ }>;
40
+ type GitCommitConfig = z.infer<typeof gitCommitConfigSchema>;
41
+ type GitCommitActionConfig = {
42
+ path?: string;
43
+ message?: string;
44
+ files?: string | string[];
45
+ all?: boolean;
46
+ verbose?: boolean;
47
+ skipEmpty?: boolean;
48
+ };
49
+
50
+ declare function gitCommitAction(_answers: unknown, config: GitCommitActionConfig & Record<string, unknown>): Promise<string>;
51
+
52
+ declare function registerGitCommitPack(plop: NodePlopAPI): void;
53
+
54
+ export { type GitCommitActionConfig, type GitCommitConfig, registerGitCommitPack as default, gitCommitAction, gitCommitConfigSchema };
@@ -0,0 +1,54 @@
1
+ import { NodePlopAPI } from 'node-plop';
2
+ import { z } from 'zod';
3
+
4
+ declare const gitCommitConfigSchema: z.ZodEffects<z.ZodObject<{
5
+ path: z.ZodString;
6
+ message: z.ZodString;
7
+ files: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
8
+ all: z.ZodOptional<z.ZodBoolean>;
9
+ verbose: z.ZodDefault<z.ZodBoolean>;
10
+ skipEmpty: z.ZodDefault<z.ZodBoolean>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ verbose: boolean;
13
+ path: string;
14
+ message: string;
15
+ skipEmpty: boolean;
16
+ files?: string | string[] | undefined;
17
+ all?: boolean | undefined;
18
+ }, {
19
+ path: string;
20
+ message: string;
21
+ verbose?: boolean | undefined;
22
+ files?: string | string[] | undefined;
23
+ all?: boolean | undefined;
24
+ skipEmpty?: boolean | undefined;
25
+ }>, {
26
+ verbose: boolean;
27
+ path: string;
28
+ message: string;
29
+ skipEmpty: boolean;
30
+ files?: string | string[] | undefined;
31
+ all?: boolean | undefined;
32
+ }, {
33
+ path: string;
34
+ message: string;
35
+ verbose?: boolean | undefined;
36
+ files?: string | string[] | undefined;
37
+ all?: boolean | undefined;
38
+ skipEmpty?: boolean | undefined;
39
+ }>;
40
+ type GitCommitConfig = z.infer<typeof gitCommitConfigSchema>;
41
+ type GitCommitActionConfig = {
42
+ path?: string;
43
+ message?: string;
44
+ files?: string | string[];
45
+ all?: boolean;
46
+ verbose?: boolean;
47
+ skipEmpty?: boolean;
48
+ };
49
+
50
+ declare function gitCommitAction(_answers: unknown, config: GitCommitActionConfig & Record<string, unknown>): Promise<string>;
51
+
52
+ declare function registerGitCommitPack(plop: NodePlopAPI): void;
53
+
54
+ export { type GitCommitActionConfig, type GitCommitConfig, registerGitCommitPack as default, gitCommitAction, gitCommitConfigSchema };
package/dist/index.js ADDED
@@ -0,0 +1,188 @@
1
+ // src/git-commit-action.ts
2
+ import { access } from "fs/promises";
3
+ import path from "path";
4
+
5
+ // src/lib/run-git.ts
6
+ import { spawn } from "child_process";
7
+ var didSucceed = (code) => code === 0;
8
+ function runGit(args, options) {
9
+ const { cwd, verbose = false } = options;
10
+ return new Promise((resolve, reject) => {
11
+ const child = spawn("git", args, {
12
+ cwd,
13
+ stdio: verbose ? "inherit" : "pipe"
14
+ });
15
+ if (verbose) {
16
+ child.on("close", (code) => {
17
+ if (didSucceed(code)) {
18
+ resolve({ stdout: "", stderr: "" });
19
+ return;
20
+ }
21
+ reject(new Error(`git ${args.join(" ")} exited with code ${code ?? "unknown"}`));
22
+ });
23
+ child.on("error", reject);
24
+ return;
25
+ }
26
+ let stdout = "";
27
+ let stderr = "";
28
+ child.stdout?.on("data", (chunk) => {
29
+ stdout += chunk.toString();
30
+ });
31
+ child.stderr?.on("data", (chunk) => {
32
+ stderr += chunk.toString();
33
+ });
34
+ child.on("close", (code) => {
35
+ if (didSucceed(code)) {
36
+ resolve({ stdout, stderr });
37
+ return;
38
+ }
39
+ const detail = stderr.trim() || stdout.trim();
40
+ reject(
41
+ new Error(
42
+ detail ? `git ${args.join(" ")} failed: ${detail}` : `git ${args.join(" ")} exited with code ${code ?? "unknown"}`
43
+ )
44
+ );
45
+ });
46
+ child.on("error", reject);
47
+ });
48
+ }
49
+
50
+ // src/lib/git-push.ts
51
+ async function hasOriginRemote(repoPath, verbose) {
52
+ try {
53
+ await runGit(["remote", "get-url", "origin"], { cwd: repoPath, verbose });
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ async function pushToOrigin(repoPath, verbose = false) {
60
+ if (!await hasOriginRemote(repoPath, verbose)) {
61
+ return {
62
+ ok: false,
63
+ warning: "Committed; push skipped: remote origin is not configured"
64
+ };
65
+ }
66
+ try {
67
+ await runGit(["push", "origin", "HEAD"], { cwd: repoPath, verbose });
68
+ return { ok: true };
69
+ } catch (error) {
70
+ const message = error instanceof Error ? error.message : String(error);
71
+ return {
72
+ ok: false,
73
+ warning: `Committed; push failed: ${message}`
74
+ };
75
+ }
76
+ }
77
+
78
+ // src/schemas/git-commit-config.ts
79
+ import { z } from "zod";
80
+ var gitCommitConfigSchema = z.object({
81
+ path: z.string().min(1),
82
+ message: z.string().trim().min(1),
83
+ files: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]).optional(),
84
+ all: z.boolean().optional(),
85
+ verbose: z.boolean().default(false),
86
+ skipEmpty: z.boolean().default(true)
87
+ }).refine((data) => data.files !== void 0 !== (data.all === true), {
88
+ message: "Provide either files or all: true, not both or neither"
89
+ });
90
+
91
+ // src/git-commit-action.ts
92
+ async function isGitRepository(repoPath) {
93
+ try {
94
+ await access(path.join(repoPath, ".git"));
95
+ return true;
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+ function formatZodError(error) {
101
+ return error.issues.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`).join("; ");
102
+ }
103
+ function normalizeFiles(files) {
104
+ return Array.isArray(files) ? files : [files];
105
+ }
106
+ async function hasChangesToStage(repoPath, verbose) {
107
+ const { stdout } = await runGit(["status", "--porcelain"], {
108
+ cwd: repoPath,
109
+ verbose
110
+ });
111
+ return stdout.trim().length > 0;
112
+ }
113
+ async function stageFiles(repoPath, config, verbose) {
114
+ if (config.files !== void 0) {
115
+ await runGit(["add", "--", ...normalizeFiles(config.files)], {
116
+ cwd: repoPath,
117
+ verbose
118
+ });
119
+ return;
120
+ }
121
+ if (config.all) {
122
+ await runGit(["add", "-A"], { cwd: repoPath, verbose });
123
+ }
124
+ }
125
+ async function hasStagedDiff(repoPath, verbose) {
126
+ try {
127
+ await runGit(["diff", "--cached", "--quiet"], { cwd: repoPath, verbose });
128
+ return false;
129
+ } catch (error) {
130
+ if (error instanceof Error && error.message.includes("exited with code 1")) {
131
+ return true;
132
+ }
133
+ throw error;
134
+ }
135
+ }
136
+ async function gitCommitAction(_answers, config) {
137
+ const parsed = gitCommitConfigSchema.safeParse({
138
+ path: config.path ?? process.cwd(),
139
+ message: config.message,
140
+ files: config.files,
141
+ all: config.all,
142
+ verbose: config.verbose ?? false,
143
+ skipEmpty: config.skipEmpty ?? true
144
+ });
145
+ if (!parsed.success) {
146
+ throw new Error(`Invalid gitCommit config: ${formatZodError(parsed.error)}`);
147
+ }
148
+ const { path: repoPath, message, verbose, skipEmpty, files, all } = parsed.data;
149
+ if (!await isGitRepository(repoPath)) {
150
+ throw new Error(`Not a git repository: ${repoPath}`);
151
+ }
152
+ const hasWorkingTreeChanges = await hasChangesToStage(repoPath, verbose);
153
+ if (!hasWorkingTreeChanges && skipEmpty) {
154
+ return "Nothing to commit";
155
+ }
156
+ if (!hasWorkingTreeChanges && !skipEmpty) {
157
+ throw new Error("Nothing to commit");
158
+ }
159
+ await stageFiles(repoPath, { files, all }, verbose);
160
+ const staged = await hasStagedDiff(repoPath, verbose);
161
+ if (!staged && skipEmpty) {
162
+ return "Nothing to commit";
163
+ }
164
+ if (!staged && !skipEmpty) {
165
+ throw new Error("Nothing to commit after staging");
166
+ }
167
+ await runGit(["commit", "-m", message], { cwd: repoPath, verbose });
168
+ const pushResult = await pushToOrigin(repoPath, verbose);
169
+ if (pushResult.ok) {
170
+ return `Committed and pushed to origin: ${message}`;
171
+ }
172
+ if (verbose) {
173
+ console.warn(pushResult.warning);
174
+ }
175
+ return pushResult.warning;
176
+ }
177
+
178
+ // src/index.ts
179
+ function registerGitCommitPack(plop) {
180
+ plop.setDefaultInclude({ actionTypes: true });
181
+ plop.setActionType("gitCommit", gitCommitAction);
182
+ }
183
+ export {
184
+ registerGitCommitPack as default,
185
+ gitCommitAction,
186
+ gitCommitConfigSchema
187
+ };
188
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/git-commit-action.ts","../src/lib/run-git.ts","../src/lib/git-push.ts","../src/schemas/git-commit-config.ts","../src/index.ts"],"sourcesContent":["import { access } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pushToOrigin } from \"./lib/git-push.js\";\nimport { runGit } from \"./lib/run-git.js\";\nimport { type GitCommitActionConfig, gitCommitConfigSchema } from \"./schemas/git-commit-config.js\";\n\nasync function isGitRepository(repoPath: string): Promise<boolean> {\n try {\n await access(path.join(repoPath, \".git\"));\n return true;\n } catch {\n return false;\n }\n}\n\nfunction formatZodError(error: {\n issues: Array<{ path: Array<string | number>; message: string }>;\n}) {\n return error.issues\n .map((issue) => `${issue.path.join(\".\") || \"config\"}: ${issue.message}`)\n .join(\"; \");\n}\n\nfunction normalizeFiles(files: string | string[]): string[] {\n return Array.isArray(files) ? files : [files];\n}\n\nasync function hasChangesToStage(repoPath: string, verbose: boolean): Promise<boolean> {\n const { stdout } = await runGit([\"status\", \"--porcelain\"], {\n cwd: repoPath,\n verbose,\n });\n return stdout.trim().length > 0;\n}\n\nasync function stageFiles(\n repoPath: string,\n config: { files?: string | string[]; all?: boolean },\n verbose: boolean,\n): Promise<void> {\n if (config.files !== undefined) {\n await runGit([\"add\", \"--\", ...normalizeFiles(config.files)], {\n cwd: repoPath,\n verbose,\n });\n return;\n }\n\n if (config.all) {\n await runGit([\"add\", \"-A\"], { cwd: repoPath, verbose });\n }\n}\n\nasync function hasStagedDiff(repoPath: string, verbose: boolean): Promise<boolean> {\n try {\n await runGit([\"diff\", \"--cached\", \"--quiet\"], { cwd: repoPath, verbose });\n return false;\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"exited with code 1\")) {\n return true;\n }\n throw error;\n }\n}\n\nexport async function gitCommitAction(\n _answers: unknown,\n config: GitCommitActionConfig & Record<string, unknown>,\n): Promise<string> {\n const parsed = gitCommitConfigSchema.safeParse({\n path: config.path ?? process.cwd(),\n message: config.message,\n files: config.files,\n all: config.all,\n verbose: config.verbose ?? false,\n skipEmpty: config.skipEmpty ?? true,\n });\n\n if (!parsed.success) {\n throw new Error(`Invalid gitCommit config: ${formatZodError(parsed.error)}`);\n }\n\n const { path: repoPath, message, verbose, skipEmpty, files, all } = parsed.data;\n\n if (!(await isGitRepository(repoPath))) {\n throw new Error(`Not a git repository: ${repoPath}`);\n }\n\n const hasWorkingTreeChanges = await hasChangesToStage(repoPath, verbose);\n\n if (!hasWorkingTreeChanges && skipEmpty) {\n return \"Nothing to commit\";\n }\n\n if (!hasWorkingTreeChanges && !skipEmpty) {\n throw new Error(\"Nothing to commit\");\n }\n\n await stageFiles(repoPath, { files, all }, verbose);\n\n const staged = await hasStagedDiff(repoPath, verbose);\n\n if (!staged && skipEmpty) {\n return \"Nothing to commit\";\n }\n\n if (!staged && !skipEmpty) {\n throw new Error(\"Nothing to commit after staging\");\n }\n\n await runGit([\"commit\", \"-m\", message], { cwd: repoPath, verbose });\n\n const pushResult = await pushToOrigin(repoPath, verbose);\n\n if (pushResult.ok) {\n return `Committed and pushed to origin: ${message}`;\n }\n\n if (verbose) {\n console.warn(pushResult.warning);\n }\n\n return pushResult.warning;\n}\n","import { spawn } from \"node:child_process\";\n\nexport type RunGitOptions = {\n cwd: string;\n verbose?: boolean;\n};\n\nexport type RunGitResult = {\n stdout: string;\n stderr: string;\n};\n\nconst didSucceed = (code: number | null): boolean => code === 0;\n\nexport function runGit(args: string[], options: RunGitOptions): Promise<RunGitResult> {\n const { cwd, verbose = false } = options;\n\n return new Promise((resolve, reject) => {\n const child = spawn(\"git\", args, {\n cwd,\n stdio: verbose ? \"inherit\" : \"pipe\",\n });\n\n if (verbose) {\n child.on(\"close\", (code) => {\n if (didSucceed(code)) {\n resolve({ stdout: \"\", stderr: \"\" });\n return;\n }\n\n reject(new Error(`git ${args.join(\" \")} exited with code ${code ?? \"unknown\"}`));\n });\n child.on(\"error\", reject);\n return;\n }\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout?.on(\"data\", (chunk: Buffer | string) => {\n stdout += chunk.toString();\n });\n\n child.stderr?.on(\"data\", (chunk: Buffer | string) => {\n stderr += chunk.toString();\n });\n\n child.on(\"close\", (code) => {\n if (didSucceed(code)) {\n resolve({ stdout, stderr });\n return;\n }\n\n const detail = stderr.trim() || stdout.trim();\n reject(\n new Error(\n detail\n ? `git ${args.join(\" \")} failed: ${detail}`\n : `git ${args.join(\" \")} exited with code ${code ?? \"unknown\"}`,\n ),\n );\n });\n\n child.on(\"error\", reject);\n });\n}\n","import { runGit } from \"./run-git.js\";\n\nexport type PushResult = { ok: true } | { ok: false; warning: string };\n\nasync function hasOriginRemote(repoPath: string, verbose: boolean): Promise<boolean> {\n try {\n await runGit([\"remote\", \"get-url\", \"origin\"], { cwd: repoPath, verbose });\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function pushToOrigin(repoPath: string, verbose = false): Promise<PushResult> {\n if (!(await hasOriginRemote(repoPath, verbose))) {\n return {\n ok: false,\n warning: \"Committed; push skipped: remote origin is not configured\",\n };\n }\n\n try {\n await runGit([\"push\", \"origin\", \"HEAD\"], { cwd: repoPath, verbose });\n return { ok: true };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n ok: false,\n warning: `Committed; push failed: ${message}`,\n };\n }\n}\n","import { z } from \"zod\";\n\nexport const gitCommitConfigSchema = z\n .object({\n path: z.string().min(1),\n message: z.string().trim().min(1),\n files: z.union([z.string().min(1), z.array(z.string().min(1)).min(1)]).optional(),\n all: z.boolean().optional(),\n verbose: z.boolean().default(false),\n skipEmpty: z.boolean().default(true),\n })\n .refine((data) => (data.files !== undefined) !== (data.all === true), {\n message: \"Provide either files or all: true, not both or neither\",\n });\n\nexport type GitCommitConfig = z.infer<typeof gitCommitConfigSchema>;\n\nexport type GitCommitActionConfig = {\n path?: string;\n message?: string;\n files?: string | string[];\n all?: boolean;\n verbose?: boolean;\n skipEmpty?: boolean;\n};\n","import type { CustomActionFunction, NodePlopAPI } from \"node-plop\";\nimport { gitCommitAction } from \"./git-commit-action.js\";\nimport {\n type GitCommitActionConfig,\n type GitCommitConfig,\n gitCommitConfigSchema,\n} from \"./schemas/git-commit-config.js\";\n\nexport { gitCommitAction, gitCommitConfigSchema };\nexport type { GitCommitActionConfig, GitCommitConfig };\n\nexport default function registerGitCommitPack(plop: NodePlopAPI): void {\n plop.setDefaultInclude({ actionTypes: true });\n plop.setActionType(\"gitCommit\", gitCommitAction as CustomActionFunction);\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "plop-pack-git-commit",
3
+ "version": "0.1.0",
4
+ "description": "PlopJS action pack to stage, commit, and push changes with git",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "peerDependencies": {
28
+ "node-plop": ">=0.25.0"
29
+ },
30
+ "dependencies": {
31
+ "zod": "^3.24.2"
32
+ },
33
+ "devDependencies": {
34
+ "@biomejs/biome": "^1.9.4",
35
+ "@types/node": "^22.13.10",
36
+ "lefthook": "^1.11.3",
37
+ "node-plop": "^0.32.0",
38
+ "tsup": "^8.4.0",
39
+ "typescript": "^5.8.2",
40
+ "vitest": "^3.0.9"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/borjalofe/plop-pack-git-commit.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/borjalofe/plop-pack-git-commit/issues"
52
+ },
53
+ "homepage": "https://github.com/borjalofe/plop-pack-git-commit#readme",
54
+ "keywords": [
55
+ "plop",
56
+ "plopjs",
57
+ "git",
58
+ "commit",
59
+ "generator"
60
+ ],
61
+ "scripts": {
62
+ "build": "tsup",
63
+ "test": "vitest run",
64
+ "lint": "biome check .",
65
+ "format": "biome format --write .",
66
+ "check": "pnpm lint && pnpm test && pnpm build"
67
+ }
68
+ }