@plainconceptsplatform/workflows 0.4.3 → 0.4.5

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.
@@ -43,6 +43,7 @@ export async function installCatalog(repositoryPath, options = {}) {
43
43
  processedContents = injectStackIntoWorkers(processedContents, defaults);
44
44
  processedContents = transformOpencodeFiles(processedContents, options.inspection);
45
45
  }
46
+ processedContents = await preserveConsumerWorkerEnv(repositoryPath, processedContents);
46
47
  const updates = [...processedContents.entries()]
47
48
  .filter(([target]) => filtered.find((file) => file.target === target)?.managed ?? true)
48
49
  .map(([target, content]) => ({ target, content }));
@@ -62,6 +63,37 @@ function injectStackIntoWorkers(files, defaults) {
62
63
  }
63
64
  return result;
64
65
  }
66
+ async function preserveConsumerWorkerEnv(repositoryPath, files) {
67
+ const result = new Map(files);
68
+ for (const [target, content] of result) {
69
+ if (!target.startsWith(".github/workflows/agent-") || !target.endsWith(".md"))
70
+ continue;
71
+ const existingPath = join(repositoryPath, target);
72
+ if (!await exists(existingPath))
73
+ continue;
74
+ result.set(target, mergeWorkerEnv(content, await readFile(existingPath, "utf8")));
75
+ }
76
+ return result;
77
+ }
78
+ function mergeWorkerEnv(packageContent, consumerContent) {
79
+ const consumerEnv = workerEnvValues(consumerContent);
80
+ if (consumerEnv.size === 0)
81
+ return packageContent;
82
+ return packageContent.replace(/^ ([A-Z][A-Z0-9_]*): .+$/gm, (line, key) => consumerEnv.has(key) ? ` ${key}: ${consumerEnv.get(key)}` : line);
83
+ }
84
+ function workerEnvValues(content) {
85
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/m.exec(content)?.[1];
86
+ const envBlock = frontmatter === undefined ? undefined : /^env:\r?\n((?: .*\r?\n?)*)/m.exec(frontmatter)?.[1];
87
+ const values = new Map();
88
+ if (envBlock === undefined)
89
+ return values;
90
+ for (const line of envBlock.split(/\r?\n/)) {
91
+ const match = /^ ([A-Z][A-Z0-9_]*): (.+)$/.exec(line);
92
+ if (match !== null)
93
+ values.set(match[1], match[2]);
94
+ }
95
+ return values;
96
+ }
65
97
  function transformOpencodeFiles(files, inspection) {
66
98
  const result = new Map(files);
67
99
  for (const [key, content] of result) {
@@ -154,6 +186,7 @@ async function validateStagedCatalog(repositoryPath, updates, compileOverride) {
154
186
  try {
155
187
  await copyCompilationInputs(repositoryPath, stagingPath);
156
188
  await writeUpdates(stagingPath, updates);
189
+ await initializeStagingRepository(stagingPath);
157
190
  const compiler = compileOverride ?? await packageCompiler(stagingPath);
158
191
  if (compiler === undefined)
159
192
  return [];
@@ -169,6 +202,9 @@ async function copyCompilationInputs(repositoryPath, stagingPath) {
169
202
  if (await exists(githubPath))
170
203
  await cp(githubPath, join(stagingPath, ".github"), { recursive: true });
171
204
  }
205
+ async function initializeStagingRepository(stagingPath) {
206
+ await execFileAsync("git", ["init", "--quiet"], { cwd: stagingPath, windowsHide: true });
207
+ }
172
208
  async function packageCompiler(repositoryPath) {
173
209
  const scriptPath = join(repositoryPath, "scripts", "compile-agent-workflows.mjs");
174
210
  if (!await exists(scriptPath))
@@ -1,4 +1,5 @@
1
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
1
+ import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { constants } from "node:fs";
2
3
  import { tmpdir } from "node:os";
3
4
  import { dirname, join } from "node:path";
4
5
  import { afterEach, describe, expect, it } from "vitest";
@@ -285,6 +286,19 @@ describe("catalog installation", () => {
285
286
  .resolves.toBe("consumer compiler\n");
286
287
  await expect(readFile(join(repositoryPath, ".husky", "pre-commit"), "utf8")).rejects.toThrow();
287
288
  });
289
+ it("preserves consumer-specific worker environment values during a forced update", async () => {
290
+ const sourcePath = await createDirectory({
291
+ "actions/check/action.yml": "name: Check\n",
292
+ "workflows/agent-check.md": "---\nenv:\n VERIFY_COMMANDS: \"package verify\"\n REPO_RULES: \"package rules\"\n OPENAI_BASE_URL: https://forge.plainconcepts.com/v1\n---\n",
293
+ "scripts/compile-agent-workflows.mjs": "compile\n",
294
+ "templates/opencode/opencode.ci.json": "{}\n",
295
+ });
296
+ const repositoryPath = await createDirectory({
297
+ ".github/workflows/agent-check.md": "---\nenv:\n VERIFY_COMMANDS: \"consumer verify\"\n REPO_RULES: \"consumer rules\"\n OPENAI_BASE_URL: https://consumer.example/v1\n---\n",
298
+ });
299
+ await installCatalog(repositoryPath, { force: true, sourcePath });
300
+ await expect(readFile(join(repositoryPath, ".github/workflows/agent-check.md"), "utf8")).resolves.toContain(" REPO_RULES: \"consumer rules\"\n OPENAI_BASE_URL: https://consumer.example/v1\n");
301
+ });
288
302
  it("applies staged generated locks with managed sources", async () => {
289
303
  const sourcePath = await createDirectory({
290
304
  "actions/check/action.yml": "package action\n",
@@ -296,6 +310,7 @@ describe("catalog installation", () => {
296
310
  await installCatalog(repositoryPath, {
297
311
  sourcePath,
298
312
  compile: async (stagingPath) => {
313
+ await expect(access(join(stagingPath, ".git"), constants.F_OK)).resolves.toBeUndefined();
299
314
  await writeFile(join(stagingPath, ".github", "workflows", "agent-check.lock.yml"), "opencode run --log-level ERROR\n", "utf8");
300
315
  },
301
316
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainconceptsplatform/workflows",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Install and update Platform GitHub agentic workflows.",
5
5
  "keywords": [
6
6
  "github-actions",