@os-eco/seeds-cli 0.2.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 +226 -0
- package/package.json +42 -0
- package/src/commands/blocked.ts +31 -0
- package/src/commands/close.ts +80 -0
- package/src/commands/create.test.ts +507 -0
- package/src/commands/create.ts +103 -0
- package/src/commands/dep.test.ts +215 -0
- package/src/commands/dep.ts +108 -0
- package/src/commands/doctor.test.ts +524 -0
- package/src/commands/doctor.ts +732 -0
- package/src/commands/init.test.ts +86 -0
- package/src/commands/init.ts +49 -0
- package/src/commands/list.ts +69 -0
- package/src/commands/migrate.ts +117 -0
- package/src/commands/onboard.test.ts +155 -0
- package/src/commands/onboard.ts +140 -0
- package/src/commands/prime.test.ts +94 -0
- package/src/commands/prime.ts +146 -0
- package/src/commands/ready.ts +31 -0
- package/src/commands/show.ts +20 -0
- package/src/commands/stats.ts +58 -0
- package/src/commands/sync.ts +76 -0
- package/src/commands/tpl.test.ts +330 -0
- package/src/commands/tpl.ts +279 -0
- package/src/commands/update.ts +97 -0
- package/src/config.ts +39 -0
- package/src/id.test.ts +55 -0
- package/src/id.ts +22 -0
- package/src/index.ts +122 -0
- package/src/markers.test.ts +73 -0
- package/src/markers.ts +19 -0
- package/src/output.ts +63 -0
- package/src/store.test.ts +173 -0
- package/src/store.ts +143 -0
- package/src/types.ts +61 -0
- package/src/yaml.test.ts +79 -0
- package/src/yaml.ts +27 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
let tmpDir: string;
|
|
7
|
+
|
|
8
|
+
const CLI = join(import.meta.dir, "../../src/index.ts");
|
|
9
|
+
|
|
10
|
+
async function run(
|
|
11
|
+
args: string[],
|
|
12
|
+
cwd: string,
|
|
13
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
14
|
+
const proc = Bun.spawn(["bun", "run", CLI, ...args], {
|
|
15
|
+
cwd,
|
|
16
|
+
stdout: "pipe",
|
|
17
|
+
stderr: "pipe",
|
|
18
|
+
});
|
|
19
|
+
const stdout = await new Response(proc.stdout).text();
|
|
20
|
+
const stderr = await new Response(proc.stderr).text();
|
|
21
|
+
const exitCode = await proc.exited;
|
|
22
|
+
return { stdout, stderr, exitCode };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function initSeeds(cwd: string): Promise<void> {
|
|
26
|
+
await run(["init"], cwd);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
tmpDir = await mkdtemp(join(tmpdir(), "seeds-prime-test-"));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("sd prime", () => {
|
|
38
|
+
test("outputs full prime content without .seeds/ initialized", async () => {
|
|
39
|
+
const { stdout, exitCode } = await run(["prime"], tmpDir);
|
|
40
|
+
expect(exitCode).toBe(0);
|
|
41
|
+
expect(stdout).toContain("Seeds Workflow Context");
|
|
42
|
+
expect(stdout).toContain("Session Close Protocol");
|
|
43
|
+
expect(stdout).toContain("sd ready");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("outputs compact content with --compact", async () => {
|
|
47
|
+
const { stdout, exitCode } = await run(["prime", "--compact"], tmpDir);
|
|
48
|
+
expect(exitCode).toBe(0);
|
|
49
|
+
expect(stdout).toContain("Seeds Quick Reference");
|
|
50
|
+
expect(stdout).not.toContain("Session Close Protocol");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("outputs JSON with --json", async () => {
|
|
54
|
+
const { stdout, exitCode } = await run(["prime", "--json"], tmpDir);
|
|
55
|
+
expect(exitCode).toBe(0);
|
|
56
|
+
const result = JSON.parse(stdout) as { success: boolean; command: string; content: string };
|
|
57
|
+
expect(result.success).toBe(true);
|
|
58
|
+
expect(result.command).toBe("prime");
|
|
59
|
+
expect(result.content).toContain("Seeds Workflow Context");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("--export outputs default template even with custom PRIME.md", async () => {
|
|
63
|
+
await initSeeds(tmpDir);
|
|
64
|
+
await Bun.write(join(tmpDir, ".seeds", "PRIME.md"), "custom prime content");
|
|
65
|
+
const { stdout, exitCode } = await run(["prime", "--export"], tmpDir);
|
|
66
|
+
expect(exitCode).toBe(0);
|
|
67
|
+
expect(stdout).toContain("Seeds Workflow Context");
|
|
68
|
+
expect(stdout).not.toContain("custom prime content");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("uses custom PRIME.md when present", async () => {
|
|
72
|
+
await initSeeds(tmpDir);
|
|
73
|
+
await Bun.write(join(tmpDir, ".seeds", "PRIME.md"), "my custom agent context");
|
|
74
|
+
const { stdout, exitCode } = await run(["prime"], tmpDir);
|
|
75
|
+
expect(exitCode).toBe(0);
|
|
76
|
+
expect(stdout).toBe("my custom agent context");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("full content includes essential command sections", async () => {
|
|
80
|
+
const { stdout } = await run(["prime"], tmpDir);
|
|
81
|
+
expect(stdout).toContain("Finding Work");
|
|
82
|
+
expect(stdout).toContain("Creating & Updating");
|
|
83
|
+
expect(stdout).toContain("Dependencies & Blocking");
|
|
84
|
+
expect(stdout).toContain("Common Workflows");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("--export with --json returns JSON", async () => {
|
|
88
|
+
const { stdout, exitCode } = await run(["prime", "--export", "--json"], tmpDir);
|
|
89
|
+
expect(exitCode).toBe(0);
|
|
90
|
+
const result = JSON.parse(stdout) as { success: boolean; content: string };
|
|
91
|
+
expect(result.success).toBe(true);
|
|
92
|
+
expect(result.content).toContain("Seeds Workflow Context");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { findSeedsDir } from "../config.ts";
|
|
3
|
+
import { outputJson } from "../output.ts";
|
|
4
|
+
|
|
5
|
+
const PRIME_FILE = "PRIME.md";
|
|
6
|
+
|
|
7
|
+
function defaultPrimeContent(compact: boolean): string {
|
|
8
|
+
if (compact) {
|
|
9
|
+
return compactContent();
|
|
10
|
+
}
|
|
11
|
+
return fullContent();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function compactContent(): string {
|
|
15
|
+
return `# Seeds Quick Reference
|
|
16
|
+
|
|
17
|
+
\`\`\`
|
|
18
|
+
sd ready # Find unblocked work
|
|
19
|
+
sd show <id> # View issue details
|
|
20
|
+
sd create --title "..." # Create issue (--type, --priority)
|
|
21
|
+
sd update <id> --status in_progress # Claim work
|
|
22
|
+
sd close <id> # Complete work
|
|
23
|
+
sd dep add <a> <b> # a depends on b
|
|
24
|
+
sd blocked # Show blocked issues
|
|
25
|
+
sd sync # Stage + commit .seeds/
|
|
26
|
+
\`\`\`
|
|
27
|
+
|
|
28
|
+
**Before finishing:** \`sd close <ids> && sd sync && git push\`
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fullContent(): string {
|
|
33
|
+
return `# Seeds Workflow Context
|
|
34
|
+
|
|
35
|
+
> **Context Recovery**: Run \`sd prime\` after compaction, clear, or new session
|
|
36
|
+
|
|
37
|
+
# Session Close Protocol
|
|
38
|
+
|
|
39
|
+
**CRITICAL**: Before saying "done" or "complete", you MUST run this checklist:
|
|
40
|
+
|
|
41
|
+
\`\`\`
|
|
42
|
+
[ ] 1. Close completed issues: sd close <id1> <id2> ...
|
|
43
|
+
[ ] 2. File issues for remaining: sd create --title "..."
|
|
44
|
+
[ ] 3. Run quality gates: bun test && bun run lint && bun run typecheck
|
|
45
|
+
[ ] 4. Sync and push: sd sync && git push
|
|
46
|
+
[ ] 5. Verify: git status (must show "up to date with origin")
|
|
47
|
+
\`\`\`
|
|
48
|
+
|
|
49
|
+
**NEVER skip this.** Work is not done until pushed.
|
|
50
|
+
|
|
51
|
+
## Core Rules
|
|
52
|
+
- **Default**: Use seeds for ALL task tracking (\`sd create\`, \`sd ready\`, \`sd close\`)
|
|
53
|
+
- **Prohibited**: Do NOT use TodoWrite, TaskCreate, or markdown files for task tracking
|
|
54
|
+
- **Workflow**: Create issues BEFORE writing code, mark in_progress when starting
|
|
55
|
+
- Git workflow: run \`sd sync\` at session end
|
|
56
|
+
|
|
57
|
+
## Essential Commands
|
|
58
|
+
|
|
59
|
+
### Finding Work
|
|
60
|
+
- \`sd ready\` — Show issues ready to work (no blockers)
|
|
61
|
+
- \`sd list --status=open\` — All open issues
|
|
62
|
+
- \`sd list --status=in_progress\` — Your active work
|
|
63
|
+
- \`sd show <id>\` — Detailed issue view with dependencies
|
|
64
|
+
|
|
65
|
+
### Creating & Updating
|
|
66
|
+
- \`sd create --title="..." --type=task|bug|feature|epic --priority=2\` — New issue
|
|
67
|
+
- Priority: 0-4 or P0-P4 (0=critical, 2=medium, 4=backlog)
|
|
68
|
+
- \`sd update <id> --status=in_progress\` — Claim work
|
|
69
|
+
- \`sd update <id> --assignee=username\` — Assign to someone
|
|
70
|
+
- \`sd close <id>\` — Mark complete
|
|
71
|
+
- \`sd close <id1> <id2> ...\` — Close multiple issues at once
|
|
72
|
+
|
|
73
|
+
### Dependencies & Blocking
|
|
74
|
+
- \`sd dep add <issue> <depends-on>\` — Add dependency
|
|
75
|
+
- \`sd dep remove <issue> <depends-on>\` — Remove dependency
|
|
76
|
+
- \`sd blocked\` — Show all blocked issues
|
|
77
|
+
|
|
78
|
+
### Sync & Project Health
|
|
79
|
+
- \`sd sync\` — Stage and commit .seeds/ changes
|
|
80
|
+
- \`sd sync --status\` — Check without committing
|
|
81
|
+
- \`sd stats\` — Project statistics
|
|
82
|
+
- \`sd doctor\` — Check for data integrity issues
|
|
83
|
+
|
|
84
|
+
## Common Workflows
|
|
85
|
+
|
|
86
|
+
**Starting work:**
|
|
87
|
+
\`\`\`bash
|
|
88
|
+
sd ready # Find available work
|
|
89
|
+
sd show <id> # Review issue details
|
|
90
|
+
sd update <id> --status=in_progress # Claim it
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
**Completing work:**
|
|
94
|
+
\`\`\`bash
|
|
95
|
+
sd close <id1> <id2> ... # Close all completed issues at once
|
|
96
|
+
sd sync # Stage + commit .seeds/
|
|
97
|
+
git push # Push to remote
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
**Creating dependent work:**
|
|
101
|
+
\`\`\`bash
|
|
102
|
+
sd create --title="Implement feature X" --type=feature
|
|
103
|
+
sd create --title="Write tests for X" --type=task
|
|
104
|
+
sd dep add <test-id> <feature-id> # Tests depend on feature
|
|
105
|
+
\`\`\`
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function run(args: string[]): Promise<void> {
|
|
110
|
+
const jsonMode = args.includes("--json");
|
|
111
|
+
const compact = args.includes("--compact");
|
|
112
|
+
const exportMode = args.includes("--export");
|
|
113
|
+
|
|
114
|
+
// --export always outputs the default template
|
|
115
|
+
if (exportMode) {
|
|
116
|
+
const content = defaultPrimeContent(false);
|
|
117
|
+
if (jsonMode) {
|
|
118
|
+
outputJson({ success: true, command: "prime", content });
|
|
119
|
+
} else {
|
|
120
|
+
process.stdout.write(content);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Try to find seeds dir for custom PRIME.md
|
|
126
|
+
let content: string | null = null;
|
|
127
|
+
try {
|
|
128
|
+
const seedsDir = await findSeedsDir();
|
|
129
|
+
const customFile = Bun.file(join(seedsDir, PRIME_FILE));
|
|
130
|
+
if (await customFile.exists()) {
|
|
131
|
+
content = await customFile.text();
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
// No seeds dir — that's fine, use default
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!content) {
|
|
138
|
+
content = defaultPrimeContent(compact);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (jsonMode) {
|
|
142
|
+
outputJson({ success: true, command: "prime", content });
|
|
143
|
+
} else {
|
|
144
|
+
process.stdout.write(content);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { findSeedsDir } from "../config.ts";
|
|
2
|
+
import { outputJson, printIssueOneLine } from "../output.ts";
|
|
3
|
+
import { readIssues } from "../store.ts";
|
|
4
|
+
import type { Issue } from "../types.ts";
|
|
5
|
+
|
|
6
|
+
export async function run(args: string[], seedsDir?: string): Promise<void> {
|
|
7
|
+
const jsonMode = args.includes("--json");
|
|
8
|
+
const dir = seedsDir ?? (await findSeedsDir());
|
|
9
|
+
const issues = await readIssues(dir);
|
|
10
|
+
|
|
11
|
+
const closedIds = new Set(issues.filter((i: Issue) => i.status === "closed").map((i) => i.id));
|
|
12
|
+
|
|
13
|
+
const ready = issues.filter((i: Issue) => {
|
|
14
|
+
if (i.status !== "open") return false;
|
|
15
|
+
const blockers = i.blockedBy ?? [];
|
|
16
|
+
return blockers.every((bid) => closedIds.has(bid));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (jsonMode) {
|
|
20
|
+
outputJson({ success: true, command: "ready", issues: ready, count: ready.length });
|
|
21
|
+
} else {
|
|
22
|
+
if (ready.length === 0) {
|
|
23
|
+
console.log("No ready issues.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const issue of ready) {
|
|
27
|
+
printIssueOneLine(issue);
|
|
28
|
+
}
|
|
29
|
+
console.log(`\n${ready.length} ready issue(s)`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { findSeedsDir } from "../config.ts";
|
|
2
|
+
import { outputJson, printIssueFull } from "../output.ts";
|
|
3
|
+
import { readIssues } from "../store.ts";
|
|
4
|
+
|
|
5
|
+
export async function run(args: string[], seedsDir?: string): Promise<void> {
|
|
6
|
+
const jsonMode = args.includes("--json");
|
|
7
|
+
const id = args.find((a) => !a.startsWith("--"));
|
|
8
|
+
if (!id) throw new Error("Usage: sd show <id>");
|
|
9
|
+
|
|
10
|
+
const dir = seedsDir ?? (await findSeedsDir());
|
|
11
|
+
const issues = await readIssues(dir);
|
|
12
|
+
const issue = issues.find((i) => i.id === id);
|
|
13
|
+
if (!issue) throw new Error(`Issue not found: ${id}`);
|
|
14
|
+
|
|
15
|
+
if (jsonMode) {
|
|
16
|
+
outputJson({ success: true, command: "show", issue });
|
|
17
|
+
} else {
|
|
18
|
+
printIssueFull(issue);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { findSeedsDir } from "../config.ts";
|
|
2
|
+
import { c, outputJson } from "../output.ts";
|
|
3
|
+
import { readIssues } from "../store.ts";
|
|
4
|
+
import type { Issue } from "../types.ts";
|
|
5
|
+
import { PRIORITY_LABELS } from "../types.ts";
|
|
6
|
+
|
|
7
|
+
export async function run(args: string[], seedsDir?: string): Promise<void> {
|
|
8
|
+
const jsonMode = args.includes("--json");
|
|
9
|
+
const dir = seedsDir ?? (await findSeedsDir());
|
|
10
|
+
const issues = await readIssues(dir);
|
|
11
|
+
|
|
12
|
+
const total = issues.length;
|
|
13
|
+
const open = issues.filter((i: Issue) => i.status === "open").length;
|
|
14
|
+
const inProgress = issues.filter((i: Issue) => i.status === "in_progress").length;
|
|
15
|
+
const closed = issues.filter((i: Issue) => i.status === "closed").length;
|
|
16
|
+
|
|
17
|
+
const closedIds = new Set(issues.filter((i: Issue) => i.status === "closed").map((i) => i.id));
|
|
18
|
+
const blocked = issues.filter((i: Issue) => {
|
|
19
|
+
if (i.status === "closed") return false;
|
|
20
|
+
return (i.blockedBy ?? []).some((bid) => !closedIds.has(bid));
|
|
21
|
+
}).length;
|
|
22
|
+
|
|
23
|
+
const byType: Record<string, number> = {};
|
|
24
|
+
for (const issue of issues) {
|
|
25
|
+
byType[issue.type] = (byType[issue.type] ?? 0) + 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const byPriority: Record<number, number> = {};
|
|
29
|
+
for (const issue of issues) {
|
|
30
|
+
byPriority[issue.priority] = (byPriority[issue.priority] ?? 0) + 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (jsonMode) {
|
|
34
|
+
outputJson({
|
|
35
|
+
success: true,
|
|
36
|
+
command: "stats",
|
|
37
|
+
stats: { total, open, inProgress, closed, blocked, byType, byPriority },
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
console.log(`${c.bold}Project Statistics${c.reset}`);
|
|
41
|
+
console.log(` Total: ${total}`);
|
|
42
|
+
console.log(` Open: ${open}`);
|
|
43
|
+
console.log(` In progress: ${inProgress}`);
|
|
44
|
+
console.log(` Closed: ${closed}`);
|
|
45
|
+
console.log(` Blocked: ${blocked}`);
|
|
46
|
+
console.log(`\n${c.bold}By Type${c.reset}`);
|
|
47
|
+
for (const [type, count] of Object.entries(byType)) {
|
|
48
|
+
console.log(` ${type.padEnd(10)} ${count}`);
|
|
49
|
+
}
|
|
50
|
+
if (Object.keys(byPriority).length > 0) {
|
|
51
|
+
console.log(`\n${c.bold}By Priority${c.reset}`);
|
|
52
|
+
for (const [p, count] of Object.entries(byPriority)) {
|
|
53
|
+
const label = PRIORITY_LABELS[Number(p)] ?? String(p);
|
|
54
|
+
console.log(` P${p} ${label.padEnd(10)} ${count}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { findSeedsDir, projectRootFromSeedsDir } from "../config.ts";
|
|
2
|
+
import { outputJson } from "../output.ts";
|
|
3
|
+
import { SEEDS_DIR_NAME } from "../types.ts";
|
|
4
|
+
|
|
5
|
+
function spawnSync(
|
|
6
|
+
cmd: string[],
|
|
7
|
+
cwd: string,
|
|
8
|
+
): { stdout: string; stderr: string; exitCode: number } {
|
|
9
|
+
const result = Bun.spawnSync(cmd, { cwd, stdout: "pipe", stderr: "pipe" });
|
|
10
|
+
const stdout = new TextDecoder().decode(result.stdout);
|
|
11
|
+
const stderr = new TextDecoder().decode(result.stderr);
|
|
12
|
+
return { stdout, stderr, exitCode: result.exitCode ?? 0 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function run(args: string[], seedsDir?: string): Promise<void> {
|
|
16
|
+
const jsonMode = args.includes("--json");
|
|
17
|
+
const statusOnly = args.includes("--status");
|
|
18
|
+
|
|
19
|
+
const dir = seedsDir ?? (await findSeedsDir());
|
|
20
|
+
const projectRoot = projectRootFromSeedsDir(dir);
|
|
21
|
+
|
|
22
|
+
const statusResult = spawnSync(
|
|
23
|
+
["git", "-C", projectRoot, "status", "--porcelain", `${SEEDS_DIR_NAME}/`],
|
|
24
|
+
projectRoot,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const changed = statusResult.stdout.trim();
|
|
28
|
+
|
|
29
|
+
if (statusOnly) {
|
|
30
|
+
if (jsonMode) {
|
|
31
|
+
outputJson({ success: true, command: "sync", hasChanges: !!changed, changes: changed });
|
|
32
|
+
} else {
|
|
33
|
+
if (changed) {
|
|
34
|
+
console.log("Uncommitted .seeds/ changes:");
|
|
35
|
+
console.log(changed);
|
|
36
|
+
} else {
|
|
37
|
+
console.log("No uncommitted .seeds/ changes.");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!changed) {
|
|
44
|
+
if (jsonMode) {
|
|
45
|
+
outputJson({
|
|
46
|
+
success: true,
|
|
47
|
+
command: "sync",
|
|
48
|
+
committed: false,
|
|
49
|
+
message: "Nothing to commit",
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
console.log("No changes to commit.");
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Stage
|
|
58
|
+
const addResult = spawnSync(["git", "-C", projectRoot, "add", `${SEEDS_DIR_NAME}/`], projectRoot);
|
|
59
|
+
if (addResult.exitCode !== 0) {
|
|
60
|
+
throw new Error(`git add failed: ${addResult.stderr}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Commit
|
|
64
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
65
|
+
const msg = `seeds: sync ${date}`;
|
|
66
|
+
const commitResult = spawnSync(["git", "-C", projectRoot, "commit", "-m", msg], projectRoot);
|
|
67
|
+
if (commitResult.exitCode !== 0) {
|
|
68
|
+
throw new Error(`git commit failed: ${commitResult.stderr}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (jsonMode) {
|
|
72
|
+
outputJson({ success: true, command: "sync", committed: true, message: msg });
|
|
73
|
+
} else {
|
|
74
|
+
console.log(`Committed: ${msg}`);
|
|
75
|
+
}
|
|
76
|
+
}
|