@samaksh/git-worker 0.2.3 → 0.3.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/dist/config/repo-presets.d.ts +16 -0
- package/dist/config/repo-presets.js +271 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/stages/apply.js +92 -71
- package/dist/stages/edit-template.js +167 -111
- package/dist/stages/eligibility.js +45 -16
- package/dist/stages/load-template.js +109 -94
- package/dist/stages/plan.js +231 -187
- package/dist/stages/report.js +23 -18
- package/dist/stages/review.js +129 -111
- package/dist/stages/select.js +115 -15
- package/package.json +1 -1
|
@@ -2,64 +2,78 @@ import { ok, err } from "../core/types.js";
|
|
|
2
2
|
import { workspaceError } from "../core/errors.js";
|
|
3
3
|
import { parseEnvTemplate, synthesizeTemplate } from "../services/template/env.js";
|
|
4
4
|
import { extractTemplateVariables } from "../services/template/interpolator.js";
|
|
5
|
+
import { detectPreset } from "../config/repo-presets.js";
|
|
5
6
|
import { sectionHeader, statusLine } from "../utils/ui.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return err(workspaceError("Prerequisites missing for template load"));
|
|
9
|
-
}
|
|
10
|
-
sectionHeader("Template");
|
|
11
|
-
const targetPath = context.target.path;
|
|
7
|
+
async function loadSingleTargetTemplate(target, context, deps) {
|
|
8
|
+
const targetPath = target.path;
|
|
12
9
|
const isEnvTarget = targetPath === ".env" || targetPath.startsWith(".env.");
|
|
10
|
+
const preset = detectPreset(context.project?.remoteUrl, context.project?.id);
|
|
11
|
+
const presetFile = preset?.files.find((f) => f.path === targetPath);
|
|
13
12
|
// 1. Try to load saved template from ~/.git-worker state
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
if (context.project) {
|
|
14
|
+
const stateResult = await deps.services.state.loadTemplate(context.project.stateDir, targetPath);
|
|
15
|
+
if (stateResult.ok && stateResult.value) {
|
|
16
|
+
const state = stateResult.value;
|
|
17
|
+
statusLine("ok", `Loaded saved template for '${targetPath}'`, `updated ${state.updatedAt}`);
|
|
18
|
+
if (state.lastRunSummary) {
|
|
19
|
+
const s = state.lastRunSummary;
|
|
20
|
+
statusLine("info", `Last run for '${targetPath}': ${s.committed} committed, ${s.failed} failed of ${s.total}`);
|
|
21
|
+
}
|
|
22
|
+
if (isEnvTarget) {
|
|
23
|
+
const parseResult = parseEnvTemplate(state.template);
|
|
24
|
+
if (parseResult.ok) {
|
|
25
|
+
return {
|
|
26
|
+
path: targetPath,
|
|
27
|
+
engine: "env-reconcile",
|
|
28
|
+
raw: state.template,
|
|
29
|
+
parsedEnv: parseResult.value,
|
|
30
|
+
isDynamic: target.isDynamic,
|
|
31
|
+
dynamicVar: target.dynamicVar,
|
|
32
|
+
description: target.description,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return {
|
|
38
|
+
path: targetPath,
|
|
39
|
+
engine: "source-of-truth",
|
|
40
|
+
raw: state.template,
|
|
41
|
+
variables: extractTemplateVariables(state.template),
|
|
42
|
+
isDynamic: target.isDynamic,
|
|
43
|
+
dynamicVar: target.dynamicVar,
|
|
44
|
+
description: target.description,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
21
47
|
}
|
|
48
|
+
}
|
|
49
|
+
// 2. Check repository preset base template catalog
|
|
50
|
+
if (presetFile && presetFile.defaultContent) {
|
|
51
|
+
statusLine("ok", `Loaded preset base template for '${targetPath}'`);
|
|
22
52
|
if (isEnvTarget) {
|
|
23
|
-
const parseResult = parseEnvTemplate(
|
|
53
|
+
const parseResult = parseEnvTemplate(presetFile.defaultContent);
|
|
24
54
|
if (parseResult.ok) {
|
|
25
|
-
|
|
55
|
+
return {
|
|
26
56
|
path: targetPath,
|
|
27
57
|
engine: "env-reconcile",
|
|
28
|
-
raw:
|
|
58
|
+
raw: presetFile.defaultContent,
|
|
29
59
|
parsedEnv: parseResult.value,
|
|
60
|
+
isDynamic: target.isDynamic,
|
|
61
|
+
dynamicVar: target.dynamicVar,
|
|
62
|
+
description: target.description || presetFile.description,
|
|
30
63
|
};
|
|
31
|
-
return ok({
|
|
32
|
-
...context,
|
|
33
|
-
template: parseResult.value,
|
|
34
|
-
activeTemplate,
|
|
35
|
-
templates: [activeTemplate],
|
|
36
|
-
});
|
|
37
64
|
}
|
|
38
65
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
template: {
|
|
49
|
-
raw: state.template,
|
|
50
|
-
assignments: [],
|
|
51
|
-
placeholders: activeTemplate.variables?.map((v) => v.name) ?? [],
|
|
52
|
-
},
|
|
53
|
-
activeTemplate,
|
|
54
|
-
templates: [activeTemplate],
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
statusLine("warn", "Saved template was invalid — synthesizing a new one");
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
statusLine("info", "No saved template — synthesizing first-run draft");
|
|
66
|
+
return {
|
|
67
|
+
path: targetPath,
|
|
68
|
+
engine: "source-of-truth",
|
|
69
|
+
raw: presetFile.defaultContent,
|
|
70
|
+
variables: extractTemplateVariables(presetFile.defaultContent),
|
|
71
|
+
isDynamic: target.isDynamic ?? presetFile.isDynamic,
|
|
72
|
+
dynamicVar: target.dynamicVar ?? presetFile.dynamicVar,
|
|
73
|
+
description: target.description || presetFile.description,
|
|
74
|
+
};
|
|
61
75
|
}
|
|
62
|
-
//
|
|
76
|
+
// 3. Try to read from existing branch content
|
|
63
77
|
if (context.eligible && context.eligible.length > 0 && context.project) {
|
|
64
78
|
if (isEnvTarget) {
|
|
65
79
|
const contents = [];
|
|
@@ -69,73 +83,59 @@ export const loadTemplateStage = async (context, deps) => {
|
|
|
69
83
|
contents.push({ branchName: branch.name, content: readResult.value });
|
|
70
84
|
}
|
|
71
85
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
templates: [activeTemplate],
|
|
87
|
-
});
|
|
86
|
+
if (contents.length > 0) {
|
|
87
|
+
const synthesized = synthesizeTemplate(contents);
|
|
88
|
+
const parseResult = parseEnvTemplate(synthesized);
|
|
89
|
+
if (parseResult.ok) {
|
|
90
|
+
statusLine("ok", `Synthesized .env template from branch content for '${targetPath}'`);
|
|
91
|
+
return {
|
|
92
|
+
path: targetPath,
|
|
93
|
+
engine: "env-reconcile",
|
|
94
|
+
raw: synthesized,
|
|
95
|
+
parsedEnv: parseResult.value,
|
|
96
|
+
isDynamic: target.isDynamic,
|
|
97
|
+
dynamicVar: target.dynamicVar,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
88
100
|
}
|
|
89
101
|
}
|
|
90
|
-
else {
|
|
102
|
+
else if (!target.isDynamic) {
|
|
91
103
|
// Source-of-truth: read sample from first branch that has the file
|
|
92
104
|
for (const branch of context.eligible) {
|
|
93
105
|
const readResult = await deps.services.git.readFileFromBranch(context.project.managedRepoPath, branch, targetPath);
|
|
94
106
|
if (readResult.ok) {
|
|
95
|
-
statusLine("ok", `Loaded draft
|
|
96
|
-
|
|
107
|
+
statusLine("ok", `Loaded draft for '${targetPath}' from branch '${branch.name}'`);
|
|
108
|
+
return {
|
|
97
109
|
path: targetPath,
|
|
98
110
|
engine: "source-of-truth",
|
|
99
111
|
raw: readResult.value,
|
|
100
112
|
variables: extractTemplateVariables(readResult.value),
|
|
113
|
+
isDynamic: target.isDynamic,
|
|
114
|
+
dynamicVar: target.dynamicVar,
|
|
101
115
|
};
|
|
102
|
-
return ok({
|
|
103
|
-
...context,
|
|
104
|
-
template: {
|
|
105
|
-
raw: readResult.value,
|
|
106
|
-
assignments: [],
|
|
107
|
-
placeholders: activeTemplate.variables?.map((v) => v.name) ?? [],
|
|
108
|
-
},
|
|
109
|
-
activeTemplate,
|
|
110
|
-
templates: [activeTemplate],
|
|
111
|
-
});
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
}
|
|
115
119
|
}
|
|
116
|
-
//
|
|
120
|
+
// 4. Fallback starters for new files
|
|
117
121
|
let starterContent = "";
|
|
118
122
|
if (isEnvTarget) {
|
|
119
123
|
starterContent =
|
|
120
124
|
"# Git Worker Template\n# Use KEY=value for static values enforced on all branches\n# Use KEY={KEY} to read existing value from each branch\n";
|
|
121
125
|
const parseResult = parseEnvTemplate(starterContent);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
const parsedEnv = parseResult.ok ? parseResult.value : undefined;
|
|
127
|
+
statusLine("info", `Using starter template for '${targetPath}'`);
|
|
128
|
+
return {
|
|
125
129
|
path: targetPath,
|
|
126
130
|
engine: "env-reconcile",
|
|
127
131
|
raw: starterContent,
|
|
128
|
-
parsedEnv
|
|
132
|
+
parsedEnv,
|
|
133
|
+
isNewFile: true,
|
|
134
|
+
isDynamic: target.isDynamic,
|
|
135
|
+
dynamicVar: target.dynamicVar,
|
|
129
136
|
};
|
|
130
|
-
statusLine("info", "Using starter template for .env");
|
|
131
|
-
return ok({
|
|
132
|
-
...context,
|
|
133
|
-
template: parseResult.value,
|
|
134
|
-
activeTemplate,
|
|
135
|
-
templates: [activeTemplate],
|
|
136
|
-
});
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
if (targetPath.endsWith(".json")) {
|
|
139
139
|
starterContent = `{\n "name": "\${BRANCH_SLUG}",\n "version": "1.0.0"\n}\n`;
|
|
140
140
|
}
|
|
141
141
|
else if (targetPath.endsWith(".yaml") || targetPath.endsWith(".yml")) {
|
|
@@ -147,22 +147,37 @@ export const loadTemplateStage = async (context, deps) => {
|
|
|
147
147
|
else {
|
|
148
148
|
starterContent = `# Git Worker Template for ${targetPath}\n`;
|
|
149
149
|
}
|
|
150
|
-
statusLine("info", `Using starter template for ${targetPath}`);
|
|
151
|
-
|
|
150
|
+
statusLine("info", `Using starter template for '${targetPath}'`);
|
|
151
|
+
return {
|
|
152
152
|
path: targetPath,
|
|
153
153
|
engine: "source-of-truth",
|
|
154
154
|
raw: starterContent,
|
|
155
155
|
variables: extractTemplateVariables(starterContent),
|
|
156
156
|
isNewFile: true,
|
|
157
|
+
isDynamic: target.isDynamic,
|
|
158
|
+
dynamicVar: target.dynamicVar,
|
|
157
159
|
};
|
|
160
|
+
}
|
|
161
|
+
export const loadTemplateStage = async (context, deps) => {
|
|
162
|
+
if (!context.project || (!context.target && !context.targets)) {
|
|
163
|
+
return err(workspaceError("Prerequisites missing for template load"));
|
|
164
|
+
}
|
|
165
|
+
sectionHeader("Template Loading");
|
|
166
|
+
const targetFiles = context.targets || (context.target ? [context.target] : []);
|
|
167
|
+
const loadedTemplates = [];
|
|
168
|
+
for (const target of targetFiles) {
|
|
169
|
+
const template = await loadSingleTargetTemplate(target, context, deps);
|
|
170
|
+
loadedTemplates.push(template);
|
|
171
|
+
}
|
|
172
|
+
const primaryTemplate = loadedTemplates[0];
|
|
158
173
|
return ok({
|
|
159
174
|
...context,
|
|
160
|
-
template: {
|
|
161
|
-
raw:
|
|
175
|
+
template: primaryTemplate.parsedEnv ?? {
|
|
176
|
+
raw: primaryTemplate.raw,
|
|
162
177
|
assignments: [],
|
|
163
|
-
placeholders:
|
|
178
|
+
placeholders: primaryTemplate.variables?.map((v) => v.name) ?? [],
|
|
164
179
|
},
|
|
165
|
-
activeTemplate,
|
|
166
|
-
templates:
|
|
180
|
+
activeTemplate: primaryTemplate,
|
|
181
|
+
templates: loadedTemplates,
|
|
167
182
|
});
|
|
168
183
|
};
|