@plainconceptsplatform/workflows 0.4.34 → 0.5.1

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.
Files changed (73) hide show
  1. package/README.md +88 -88
  2. package/loops/actions/agent-output.cjs +17 -17
  3. package/loops/actions/audit-close/action.yml +24 -0
  4. package/loops/actions/classify-route/action.yml +1 -3
  5. package/loops/actions/classify-route/classify-route.sh +40 -35
  6. package/loops/actions/update-changelog/action.yml +113 -0
  7. package/loops/actions/validate-merge-gate-output/action.yml +40 -40
  8. package/loops/actions/validate-refine-output/validate-refine-output.sh +16 -3
  9. package/loops/actions/validate-review-output/action.yml +35 -35
  10. package/loops/actions/validate-triage-output/action.yml +36 -36
  11. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +91 -36
  12. package/loops/scripts/compile-agent-workflows.mjs +195 -6
  13. package/loops/templates/agentics/agentics-checks.yml +86 -86
  14. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  15. package/loops/templates/ci/app-ci-dotnet-next.yml +268 -171
  16. package/loops/templates/ci/app-ci-node-monorepo.yml +208 -178
  17. package/loops/templates/issues/bug_report.yml +109 -109
  18. package/loops/templates/issues/feature_request.yml +75 -75
  19. package/loops/templates/opencode/opencode.ci.json +49 -47
  20. package/loops/templates/opencode/opencode.ci.json.md +46 -41
  21. package/loops/templates/release/github-release.yml +30 -30
  22. package/loops/workflows/agent-apply-review.md +465 -443
  23. package/loops/workflows/agent-audit.md +213 -197
  24. package/loops/workflows/agent-implement.md +538 -414
  25. package/loops/workflows/agent-merge-gate.md +730 -584
  26. package/loops/workflows/agent-refine.md +609 -418
  27. package/loops/workflows/agent-release.md +258 -0
  28. package/loops/workflows/agent-triage.md +447 -439
  29. package/loops/workflows/authorize-bot-work.yml +85 -82
  30. package/loops/workflows/shared/opencode-ci.md +206 -197
  31. package/loops/workflows/shared/platform-defaults.md +19 -16
  32. package/loops/workflows/work-router.yml +862 -632
  33. package/package.json +7 -8
  34. package/dist/action-validation.test.d.ts +0 -1
  35. package/dist/action-validation.test.js +0 -84
  36. package/dist/catalog-installation.d.ts +0 -30
  37. package/dist/catalog-installation.js +0 -379
  38. package/dist/catalog-installation.test.d.ts +0 -1
  39. package/dist/catalog-installation.test.js +0 -448
  40. package/dist/catalog-listing.d.ts +0 -13
  41. package/dist/catalog-listing.js +0 -70
  42. package/dist/catalog-listing.test.d.ts +0 -1
  43. package/dist/catalog-listing.test.js +0 -150
  44. package/dist/index.d.ts +0 -2
  45. package/dist/index.js +0 -218
  46. package/dist/index.test.d.ts +0 -1
  47. package/dist/index.test.js +0 -273
  48. package/dist/repository-inspection.d.ts +0 -23
  49. package/dist/repository-inspection.js +0 -113
  50. package/dist/repository-inspection.test.d.ts +0 -1
  51. package/dist/repository-inspection.test.js +0 -77
  52. package/dist/repository-state.d.ts +0 -18
  53. package/dist/repository-state.js +0 -77
  54. package/dist/repository-state.test.d.ts +0 -1
  55. package/dist/repository-state.test.js +0 -96
  56. package/dist/route-processing.d.ts +0 -6
  57. package/dist/route-processing.js +0 -150
  58. package/dist/route-processing.test.d.ts +0 -1
  59. package/dist/route-processing.test.js +0 -350
  60. package/dist/stack-defaults.d.ts +0 -11
  61. package/dist/stack-defaults.js +0 -104
  62. package/dist/stack-defaults.test.d.ts +0 -1
  63. package/dist/stack-defaults.test.js +0 -266
  64. package/dist/tui.d.ts +0 -25
  65. package/dist/tui.js +0 -287
  66. package/dist/tui.test.d.ts +0 -1
  67. package/dist/tui.test.js +0 -246
  68. package/dist/workflow-catalog.d.ts +0 -25
  69. package/dist/workflow-catalog.js +0 -59
  70. package/dist/workflow-catalog.test.d.ts +0 -1
  71. package/dist/workflow-catalog.test.js +0 -29
  72. package/loops/workflows/agent-direct.md +0 -375
  73. package/loops/workflows/agent-propose.md +0 -342
@@ -1,23 +0,0 @@
1
- export type RepositoryVisibility = "public" | "private";
2
- export type VisibilitySource = "argument" | "environment" | "github" | "fallback";
3
- export interface StackHints {
4
- readonly packageJson: boolean;
5
- readonly pnpmLockfile: boolean;
6
- readonly solutionFiles: readonly string[];
7
- readonly openSpec: boolean;
8
- }
9
- export interface RepositoryInspection {
10
- readonly repositoryPath: string;
11
- readonly existingAgentWorkflows: readonly string[];
12
- readonly stackHints: StackHints;
13
- }
14
- export interface VisibilityResolution {
15
- readonly value: RepositoryVisibility;
16
- readonly source: VisibilitySource;
17
- }
18
- export interface CommandRunner {
19
- readonly run: (command: string, arguments_: readonly string[], cwd: string) => Promise<string>;
20
- }
21
- export declare function inspectRepository(repositoryPath: string): Promise<RepositoryInspection>;
22
- export declare function resolveVisibility(repositoryPath: string, override: RepositoryVisibility | undefined, environment?: NodeJS.ProcessEnv, commandRunner?: CommandRunner): Promise<VisibilityResolution>;
23
- export declare function parseVisibility(value: string | undefined): RepositoryVisibility | undefined;
@@ -1,113 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
3
- import { access, readdir, stat } from "node:fs/promises";
4
- import { constants } from "node:fs";
5
- import { join } from "node:path";
6
- const execFileAsync = promisify(execFile);
7
- const defaultCommandRunner = {
8
- async run(command, arguments_, cwd) {
9
- const { stdout } = await execFileAsync(command, arguments_, { cwd, windowsHide: true });
10
- return stdout;
11
- },
12
- };
13
- export async function inspectRepository(repositoryPath) {
14
- const workflowsPath = join(repositoryPath, ".github", "workflows");
15
- const workflowEntries = await readDirectoryNames(workflowsPath);
16
- const solutionFiles = await findSolutionFiles(repositoryPath);
17
- return {
18
- repositoryPath,
19
- existingAgentWorkflows: workflowEntries.filter((entry) => /^agent-.*\.md$/i.test(entry)).sort(),
20
- stackHints: {
21
- packageJson: await pathExists(join(repositoryPath, "package.json")),
22
- pnpmLockfile: await pathExists(join(repositoryPath, "pnpm-lock.yaml")),
23
- solutionFiles,
24
- openSpec: await isDirectory(join(repositoryPath, "openspec")),
25
- },
26
- };
27
- }
28
- export async function resolveVisibility(repositoryPath, override, environment = process.env, commandRunner = defaultCommandRunner) {
29
- if (override !== undefined)
30
- return { value: override, source: "argument" };
31
- const environmentOverride = parseVisibility(environment.PLATFORM_WORKFLOWS_VISIBILITY);
32
- if (environmentOverride !== undefined)
33
- return { value: environmentOverride, source: "environment" };
34
- try {
35
- const output = await commandRunner.run("gh", ["repo", "view", "--json", "visibility"], repositoryPath);
36
- const visibility = parseVisibilityFromGitHub(output);
37
- if (visibility !== undefined)
38
- return { value: visibility, source: "github" };
39
- }
40
- catch {
41
- // GitHub CLI is optional. Initializing a local repository must still work offline.
42
- }
43
- return { value: "private", source: "fallback" };
44
- }
45
- export function parseVisibility(value) {
46
- return value === "public" || value === "private" ? value : undefined;
47
- }
48
- async function findSolutionFiles(repositoryPath) {
49
- const results = [];
50
- await scanForSlnx(repositoryPath, "", results, 0);
51
- return results.sort();
52
- }
53
- const MAX_DEPTH = 5;
54
- const SKIP_DIRS = new Set(["node_modules", ".git", ".next", "dist", "out", "build", ".turbo", ".cache"]);
55
- async function scanForSlnx(root, relativePath, results, depth) {
56
- if (depth >= MAX_DEPTH)
57
- return;
58
- const currentPath = join(root, relativePath);
59
- let entries;
60
- try {
61
- entries = await readdir(currentPath, { withFileTypes: true });
62
- }
63
- catch {
64
- return;
65
- }
66
- for (const entry of entries) {
67
- if (entry.isDirectory()) {
68
- if (SKIP_DIRS.has(entry.name))
69
- continue;
70
- await scanForSlnx(root, join(relativePath, entry.name), results, depth + 1);
71
- }
72
- else if (entry.isFile() && entry.name.endsWith(".slnx")) {
73
- results.push(relativePath === "" ? entry.name : join(relativePath, entry.name));
74
- }
75
- }
76
- }
77
- async function pathExists(path) {
78
- try {
79
- await access(path, constants.F_OK);
80
- return true;
81
- }
82
- catch {
83
- return false;
84
- }
85
- }
86
- async function isDirectory(path) {
87
- try {
88
- return (await stat(path)).isDirectory();
89
- }
90
- catch {
91
- return false;
92
- }
93
- }
94
- async function readDirectoryNames(path) {
95
- try {
96
- return await readdir(path);
97
- }
98
- catch {
99
- return [];
100
- }
101
- }
102
- function parseVisibilityFromGitHub(output) {
103
- try {
104
- const parsed = JSON.parse(output);
105
- if (typeof parsed !== "object" || parsed === null || !("visibility" in parsed))
106
- return undefined;
107
- const { visibility } = parsed;
108
- return typeof visibility === "string" ? parseVisibility(visibility.toLowerCase()) : undefined;
109
- }
110
- catch {
111
- return undefined;
112
- }
113
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,77 +0,0 @@
1
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { inspectRepository, resolveVisibility } from "./repository-inspection.js";
6
- import { run } from "./index.js";
7
- const temporaryDirectories = [];
8
- afterEach(async () => {
9
- await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true })));
10
- });
11
- describe("repository inspection", () => {
12
- it("finds agent workflows and supported stack markers", async () => {
13
- const repositoryPath = await createRepository({
14
- ".github/workflows/agent-refine.md": "# Refine",
15
- ".github/workflows/agent-custom.md": "# Custom",
16
- ".github/workflows/other.md": "# Other",
17
- "package.json": "{}",
18
- "pnpm-lock.yaml": "lockfileVersion: '9.0'",
19
- "apps/api/Numa.slnx": "<Solution />",
20
- "openspec/changes/.gitkeep": "",
21
- });
22
- await expect(inspectRepository(repositoryPath)).resolves.toMatchObject({
23
- existingAgentWorkflows: ["agent-custom.md", "agent-refine.md"],
24
- stackHints: {
25
- packageJson: true,
26
- pnpmLockfile: true,
27
- solutionFiles: [join("apps", "api", "Numa.slnx")],
28
- openSpec: true,
29
- },
30
- });
31
- });
32
- it("uses an explicit visibility before environment or GitHub", async () => {
33
- const runner = { run: async () => '{"visibility":"public"}' };
34
- await expect(resolveVisibility("repo", "private", { PLATFORM_WORKFLOWS_VISIBILITY: "public" }, runner)).resolves.toEqual({
35
- value: "private",
36
- source: "argument",
37
- });
38
- });
39
- it("falls back to private when GitHub CLI is unavailable", async () => {
40
- const runner = { run: async () => Promise.reject(new Error("missing gh")) };
41
- await expect(resolveVisibility("repo", undefined, {}, runner)).resolves.toEqual({ value: "private", source: "fallback" });
42
- });
43
- });
44
- describe("CLI commands", () => {
45
- it("keeps init available without writing configuration", async () => {
46
- const repositoryPath = await createRepository({ "package.json": "{}" });
47
- const output = captureConsole("log");
48
- await expect(run(["init", "--visibility", "public"], repositoryPath)).resolves.toBe(0);
49
- expect(output.calls).toHaveLength(1);
50
- await expect(import("node:fs/promises").then(({ access }) => access(join(repositoryPath, ".github/workflows/shared/repo-config.md")))).rejects.toThrow();
51
- output.restore();
52
- });
53
- it("accepts update as an add alias", async () => {
54
- const repositoryPath = await createRepository({});
55
- const error = captureConsole("error");
56
- await expect(run(["update", "--invalid"], repositoryPath)).resolves.toBe(1);
57
- expect(error.calls).toEqual(["Unknown option: --invalid"]);
58
- error.restore();
59
- });
60
- });
61
- function captureConsole(method) {
62
- const original = console[method];
63
- const calls = [];
64
- console[method] = (message) => calls.push(message);
65
- return { calls, restore: () => { console[method] = original; } };
66
- }
67
- async function createRepository(files) {
68
- const repositoryPath = await mkdtemp(join(tmpdir(), "workflows-"));
69
- temporaryDirectories.push(repositoryPath);
70
- await Promise.all(Object.entries(files).map(async ([relativePath, content]) => {
71
- const path = join(repositoryPath, relativePath);
72
- const { mkdir } = await import("node:fs/promises");
73
- await mkdir(join(path, ".."), { recursive: true });
74
- await writeFile(path, content, "utf8");
75
- }));
76
- return repositoryPath;
77
- }
@@ -1,18 +0,0 @@
1
- import type { RepositoryInspection, RepositoryVisibility, VisibilityResolution } from "./repository-inspection.js";
2
- export declare const manifestRelativePath = ".github/workflows/shared/platform-workflows.json";
3
- export declare const repositoryConfigRelativePath = ".github/workflows/shared/repo-config.md";
4
- export interface PlatformWorkflowsManifest {
5
- readonly schemaVersion: 1;
6
- readonly repositoryPath: string;
7
- readonly initializedAt: string;
8
- readonly visibility: RepositoryVisibility;
9
- readonly visibilitySource: VisibilityResolution["source"];
10
- readonly existingAgentWorkflows: readonly string[];
11
- readonly stackHints: RepositoryInspection["stackHints"];
12
- }
13
- export interface InitializationResult {
14
- readonly manifest: PlatformWorkflowsManifest;
15
- readonly repositoryConfigCreated: boolean;
16
- }
17
- export declare function initializeRepository(inspection: RepositoryInspection, visibility: VisibilityResolution, now?: Date): Promise<InitializationResult>;
18
- export declare function readManifest(repositoryPath: string): Promise<PlatformWorkflowsManifest | undefined>;
@@ -1,77 +0,0 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
- import { dirname, join } from "node:path";
3
- export const manifestRelativePath = ".github/workflows/shared/platform-workflows.json";
4
- export const repositoryConfigRelativePath = ".github/workflows/shared/repo-config.md";
5
- export async function initializeRepository(inspection, visibility, now = new Date()) {
6
- const configPath = join(inspection.repositoryPath, repositoryConfigRelativePath);
7
- const repositoryConfigCreated = await writeRepositoryConfigIfMissing(configPath, inspection, visibility.value);
8
- const manifest = {
9
- schemaVersion: 1,
10
- repositoryPath: inspection.repositoryPath,
11
- initializedAt: now.toISOString(),
12
- visibility: visibility.value,
13
- visibilitySource: visibility.source,
14
- existingAgentWorkflows: inspection.existingAgentWorkflows,
15
- stackHints: inspection.stackHints,
16
- };
17
- await writeFile(join(inspection.repositoryPath, manifestRelativePath), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
18
- return { manifest, repositoryConfigCreated };
19
- }
20
- export async function readManifest(repositoryPath) {
21
- try {
22
- const content = await readFile(join(repositoryPath, manifestRelativePath), "utf8");
23
- return parseManifest(JSON.parse(content));
24
- }
25
- catch {
26
- return undefined;
27
- }
28
- }
29
- async function writeRepositoryConfigIfMissing(configPath, inspection, visibility) {
30
- try {
31
- await readFile(configPath, "utf8");
32
- return false;
33
- }
34
- catch {
35
- await mkdir(dirname(configPath), { recursive: true });
36
- await writeFile(configPath, createRepositoryConfig(inspection, visibility), { encoding: "utf8", flag: "wx" });
37
- return true;
38
- }
39
- }
40
- function createRepositoryConfig(inspection, visibility) {
41
- const stack = [
42
- inspection.stackHints.packageJson ? "- Node.js (`package.json`)" : undefined,
43
- inspection.stackHints.pnpmLockfile ? "- pnpm (`pnpm-lock.yaml`)" : undefined,
44
- ...inspection.stackHints.solutionFiles.map((file) => `- .NET solution (${file})`),
45
- inspection.stackHints.openSpec ? "- OpenSpec (`openspec/`)" : undefined,
46
- ].filter((hint) => hint !== undefined);
47
- return [
48
- "# Repository workflow configuration",
49
- "",
50
- `- Visibility: ${visibility}`,
51
- "",
52
- "## Detected stack",
53
- "",
54
- ...(stack.length > 0 ? stack : ["- No supported stack markers detected"]),
55
- "",
56
- "## Verification commands",
57
- "",
58
- "Add repository-specific verification commands here.",
59
- "",
60
- "## Repository rules",
61
- "",
62
- "Add repository-specific workflow rules here.",
63
- "",
64
- ].join("\n");
65
- }
66
- function parseManifest(value) {
67
- if (typeof value !== "object" || value === null)
68
- return undefined;
69
- const manifest = value;
70
- if (manifest.schemaVersion !== 1 || typeof manifest.repositoryPath !== "string" || typeof manifest.initializedAt !== "string")
71
- return undefined;
72
- if (manifest.visibility !== "public" && manifest.visibility !== "private")
73
- return undefined;
74
- if (!Array.isArray(manifest.existingAgentWorkflows) || typeof manifest.stackHints !== "object" || manifest.stackHints === null)
75
- return undefined;
76
- return manifest;
77
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,96 +0,0 @@
1
- import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, describe, expect, it } from "vitest";
5
- import { inspectRepository, resolveVisibility } from "./repository-inspection.js";
6
- import { initializeRepository, manifestRelativePath, readManifest, repositoryConfigRelativePath } from "./repository-state.js";
7
- import { run } from "./index.js";
8
- const temporaryDirectories = [];
9
- afterEach(async () => {
10
- await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true })));
11
- });
12
- describe("repository inspection", () => {
13
- it("finds agent workflows and supported stack markers", async () => {
14
- const repositoryPath = await createRepository({
15
- ".github/workflows/agent-refine.md": "# Refine",
16
- ".github/workflows/agent-custom.md": "# Custom",
17
- ".github/workflows/other.md": "# Other",
18
- "package.json": "{}",
19
- "pnpm-lock.yaml": "lockfileVersion: '9.0'",
20
- "apps/api/Numa.slnx": "<Solution />",
21
- "openspec/changes/.gitkeep": "",
22
- });
23
- await expect(inspectRepository(repositoryPath)).resolves.toMatchObject({
24
- existingAgentWorkflows: ["agent-custom.md", "agent-refine.md"],
25
- stackHints: {
26
- packageJson: true,
27
- pnpmLockfile: true,
28
- solutionFiles: [join(repositoryPath, "apps", "api", "Numa.slnx")],
29
- openSpec: true,
30
- },
31
- });
32
- });
33
- it("uses an explicit visibility before environment or GitHub", async () => {
34
- const runner = { run: async () => '{"visibility":"public"}' };
35
- await expect(resolveVisibility("repo", "private", { PLATFORM_WORKFLOWS_VISIBILITY: "public" }, runner)).resolves.toEqual({
36
- value: "private",
37
- source: "argument",
38
- });
39
- });
40
- it("falls back to private when GitHub CLI is unavailable", async () => {
41
- const runner = { run: async () => Promise.reject(new Error("missing gh")) };
42
- await expect(resolveVisibility("repo", undefined, {}, runner)).resolves.toEqual({ value: "private", source: "fallback" });
43
- });
44
- });
45
- describe("repository initialization", () => {
46
- it("creates config once and updates the managed manifest", async () => {
47
- const repositoryPath = await createRepository({ "package.json": "{}" });
48
- const inspection = await inspectRepository(repositoryPath);
49
- const first = await initializeRepository(inspection, { value: "public", source: "argument" }, new Date("2026-08-12T00:00:00.000Z"));
50
- const configPath = join(repositoryPath, repositoryConfigRelativePath);
51
- await writeFile(configPath, "consumer settings\n", "utf8");
52
- const second = await initializeRepository(inspection, { value: "private", source: "fallback" }, new Date("2026-08-13T00:00:00.000Z"));
53
- expect(first.repositoryConfigCreated).toBe(true);
54
- expect(second.repositoryConfigCreated).toBe(false);
55
- await expect(readFile(configPath, "utf8")).resolves.toBe("consumer settings\n");
56
- await expect(readManifest(repositoryPath)).resolves.toMatchObject({
57
- initializedAt: "2026-08-13T00:00:00.000Z",
58
- visibility: "private",
59
- visibilitySource: "fallback",
60
- });
61
- await expect(readFile(join(repositoryPath, manifestRelativePath), "utf8")).resolves.toContain('"schemaVersion": 1');
62
- });
63
- });
64
- describe("CLI commands", () => {
65
- it("keeps init available", async () => {
66
- const repositoryPath = await createRepository({ "package.json": "{}" });
67
- const output = captureConsole("log");
68
- await expect(run(["init", "--visibility", "public"], repositoryPath)).resolves.toBe(0);
69
- expect(output.calls).toHaveLength(1);
70
- output.restore();
71
- });
72
- it("accepts update as an add alias", async () => {
73
- const repositoryPath = await createRepository({});
74
- const error = captureConsole("error");
75
- await expect(run(["update", "--invalid"], repositoryPath)).resolves.toBe(1);
76
- expect(error.calls).toEqual(["update accepts only --force."]);
77
- error.restore();
78
- });
79
- });
80
- function captureConsole(method) {
81
- const original = console[method];
82
- const calls = [];
83
- console[method] = (message) => calls.push(message);
84
- return { calls, restore: () => { console[method] = original; } };
85
- }
86
- async function createRepository(files) {
87
- const repositoryPath = await mkdtemp(join(tmpdir(), "platform-workflows-"));
88
- temporaryDirectories.push(repositoryPath);
89
- await Promise.all(Object.entries(files).map(async ([relativePath, content]) => {
90
- const path = join(repositoryPath, relativePath);
91
- const { mkdir } = await import("node:fs/promises");
92
- await mkdir(join(path, ".."), { recursive: true });
93
- await writeFile(path, content, "utf8");
94
- }));
95
- return repositoryPath;
96
- }
@@ -1,6 +0,0 @@
1
- import { type RouteName } from "./workflow-catalog.js";
2
- export declare function stripRouteFromRouter(yaml: string, route: RouteName): string;
3
- export declare function stripRouteFromClassifier(shell: string, route: RouteName): string;
4
- export declare function addRouteExclusion(matrix: string, route: RouteName): string;
5
- export declare function processRoutes(files: Map<string, string>, selectedRoutes: readonly RouteName[]): Map<string, string>;
6
- export declare function excludedWorkerFiles(selectedRoutes: readonly RouteName[]): Set<string>;
@@ -1,150 +0,0 @@
1
- import { routeNames, workflowRoutes } from "./workflow-catalog.js";
2
- const routeCrons = {
3
- audit: "17 1 * * 1",
4
- propose: "29 7 * * *",
5
- };
6
- export function stripRouteFromRouter(yaml, route) {
7
- let result = yaml;
8
- const cron = routeCrons[route];
9
- if (cron !== undefined) {
10
- result = result.replace(new RegExp(`^ - cron: "${escapeRegex(cron)}"\\n`, "gm"), "");
11
- }
12
- result = removeJobBlock(result, `call-${route}`);
13
- result = result.replace(new RegExp(`^ - ${escapeRegex(route)}\\n`, "gm"), "");
14
- return result;
15
- }
16
- export function stripRouteFromClassifier(shell, route) {
17
- const constName = route.replace(/-/g, "_").toUpperCase() + "_CRON";
18
- let result = shell;
19
- result = result.replace(new RegExp(`^readonly ${constName}="[^"]*"\\n`, "gm"), "");
20
- const cron = routeCrons[route];
21
- if (cron !== undefined) {
22
- result = result.replace(new RegExp(`\\s*"\\$${constName}"\\) route="${escapeRegex(route)}" ;;\\n`, "g"), "");
23
- }
24
- result = result.replace(new RegExp(` \\| ${escapeRegex(route)}\\)`, "g"), ")");
25
- result = result.replace(new RegExp(`^(\\s+)${escapeRegex(route)} \\| `, "gm"), "$1");
26
- if (!result.includes(`| ${route}`) && !result.includes(`${route} |`)) {
27
- result = removeAloneDispatchCase(result, route);
28
- }
29
- return result;
30
- }
31
- function removeAloneDispatchCase(shell, route) {
32
- const lines = shell.split("\n");
33
- const pattern = new RegExp(`^(\\s+)${escapeRegex(route)}\\)\\s*$`);
34
- const result = [];
35
- let i = 0;
36
- while (i < lines.length) {
37
- const match = lines[i].match(pattern);
38
- if (match) {
39
- const indent = match[1];
40
- i++;
41
- while (i < lines.length) {
42
- if (new RegExp(`^${escapeRegex(indent)};;\\s*$`).test(lines[i])) {
43
- i++;
44
- break;
45
- }
46
- i++;
47
- }
48
- }
49
- else {
50
- result.push(lines[i]);
51
- i++;
52
- }
53
- }
54
- return result.join("\n");
55
- }
56
- export function addRouteExclusion(matrix, route) {
57
- if (matrix.includes(`excluded route '${route}'`))
58
- return matrix;
59
- let result = matrix;
60
- result = result.replace(new RegExp(` ${escapeRegex(route)} `, "g"), " ");
61
- const exclusionBlock = `\necho "── Excluded routes ──────────────────────────────────────────────────────"\nif ! grep -q "route == '${route}'" "$ROUTER_YML"; then\n PASS=$((PASS + 1))\n echo " ${route} correctly excluded from work-router.yml"\nelse\n FAIL=$((FAIL + 1))\n echo "FAIL: excluded route '${route}' is still in work-router.yml" >&2\nfi\n`;
62
- result = result.replace(/(\necho\nif \[ "\$FAIL" -eq 0 \])/, `${exclusionBlock}$1`);
63
- return result;
64
- }
65
- export function processRoutes(files, selectedRoutes) {
66
- const excludedRoutes = routeNames.filter((r) => !selectedRoutes.includes(r));
67
- if (excludedRoutes.length === 0)
68
- return files;
69
- const result = new Map(files);
70
- for (const route of excludedRoutes) {
71
- const routerKey = findFileKey(result, "work-router.yml");
72
- if (routerKey !== undefined) {
73
- result.set(routerKey, stripRouteFromRouter(result.get(routerKey), route));
74
- }
75
- const matrixKey = findFileKey(result, "verify-route-matrix.sh");
76
- if (matrixKey !== undefined) {
77
- result.set(matrixKey, addRouteExclusion(result.get(matrixKey), route));
78
- }
79
- }
80
- const matrixKey = findFileKey(result, "verify-route-matrix.sh");
81
- if (matrixKey !== undefined) {
82
- result.set(matrixKey, createRouteMatrix(selectedRoutes));
83
- }
84
- return result;
85
- }
86
- function createRouteMatrix(selectedRoutes) {
87
- const selected = selectedRoutes.join(" ");
88
- const excluded = routeNames.filter((route) => !selectedRoutes.includes(route)).join(" ");
89
- return `#!/usr/bin/env bash
90
- set -euo pipefail
91
-
92
- HERE="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
93
- ROUTER_YML="\${HERE}/../../workflows/work-router.yml"
94
- CLASSIFIER="\${HERE}/../classify-route/classify-route.sh"
95
-
96
- bash -n "$CLASSIFIER"
97
-
98
- for route in ${selected}; do
99
- grep -q "route == '$route'" "$ROUTER_YML" || {
100
- echo "FAIL: selected route '$route' has no router job" >&2
101
- exit 1
102
- }
103
- done
104
-
105
- for route in ${excluded}; do
106
- if grep -q "route == '$route'" "$ROUTER_YML"; then
107
- echo "FAIL: excluded route '$route' remains in router" >&2
108
- exit 1
109
- fi
110
- done
111
-
112
- echo "Route matrix: selected routes valid"
113
- `;
114
- }
115
- export function excludedWorkerFiles(selectedRoutes) {
116
- return new Set(workflowRoutes
117
- .filter((route) => !selectedRoutes.includes(route.name))
118
- .map((route) => route.worker));
119
- }
120
- function removeJobBlock(yaml, jobName) {
121
- const lines = yaml.split("\n");
122
- const startPattern = new RegExp(`^ ${escapeRegex(jobName)}:`);
123
- const result = [];
124
- let skipping = false;
125
- for (const line of lines) {
126
- if (skipping) {
127
- if (/^ \S/.test(line) || /^[^\s]/.test(line)) {
128
- skipping = false;
129
- result.push(line);
130
- }
131
- }
132
- else if (startPattern.test(line)) {
133
- skipping = true;
134
- }
135
- else {
136
- result.push(line);
137
- }
138
- }
139
- return result.join("\n");
140
- }
141
- function findFileKey(files, endsWith) {
142
- for (const key of files.keys()) {
143
- if (key.endsWith(endsWith))
144
- return key;
145
- }
146
- return undefined;
147
- }
148
- function escapeRegex(str) {
149
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
150
- }
@@ -1 +0,0 @@
1
- export {};