@samaksh/git-worker 0.2.2 → 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.
@@ -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
- export const loadTemplateStage = async (context, deps) => {
7
- if (!context.project || !context.target) {
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
- 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", `last updated ${state.updatedAt}`);
18
- if (state.lastRunSummary) {
19
- const s = state.lastRunSummary;
20
- statusLine("info", `Last run: ${s.committed} committed, ${s.failed} failed, ${s.skipped} skipped of ${s.total}`);
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(state.template);
53
+ const parseResult = parseEnvTemplate(presetFile.defaultContent);
24
54
  if (parseResult.ok) {
25
- const activeTemplate = {
55
+ return {
26
56
  path: targetPath,
27
57
  engine: "env-reconcile",
28
- raw: state.template,
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
- else {
40
- const activeTemplate = {
41
- path: targetPath,
42
- engine: "source-of-truth",
43
- raw: state.template,
44
- variables: extractTemplateVariables(state.template),
45
- };
46
- return ok({
47
- ...context,
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
- // 2. First run: synthesize from existing branch content
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
- const synthesized = synthesizeTemplate(contents);
73
- const parseResult = parseEnvTemplate(synthesized);
74
- if (parseResult.ok) {
75
- statusLine("ok", "Synthesized .env template from branch content");
76
- const activeTemplate = {
77
- path: targetPath,
78
- engine: "env-reconcile",
79
- raw: synthesized,
80
- parsedEnv: parseResult.value,
81
- };
82
- return ok({
83
- ...context,
84
- template: parseResult.value,
85
- activeTemplate,
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 template from branch '${branch.name}'`);
96
- const activeTemplate = {
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
- // 3. Fallback starters for new files
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
- if (!parseResult.ok)
123
- return parseResult;
124
- const activeTemplate = {
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: parseResult.value,
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
- else if (targetPath.endsWith(".json")) {
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
- const activeTemplate = {
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: starterContent,
175
+ template: primaryTemplate.parsedEnv ?? {
176
+ raw: primaryTemplate.raw,
162
177
  assignments: [],
163
- placeholders: activeTemplate.variables?.map((v) => v.name) ?? [],
178
+ placeholders: primaryTemplate.variables?.map((v) => v.name) ?? [],
164
179
  },
165
- activeTemplate,
166
- templates: [activeTemplate],
180
+ activeTemplate: primaryTemplate,
181
+ templates: loadedTemplates,
167
182
  });
168
183
  };