@lumpcode/recipes 0.3.0 → 0.3.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.
package/README.md CHANGED
@@ -51,6 +51,16 @@ Flat helpers under `src/kit/` (re-exported from the package root):
51
51
  - `ymlBacklogContexts` / `setTaskDoneStep` — **deprecated** YAML-list helpers (warn once, still work)
52
52
  - `resolveImplValidateCommand` — string, descriptor, or fn → `ValidationCommandFn`
53
53
  - `shellCommand` — `sh -c` helper for validation commands
54
+ - `openPrPostTeardown` — `postTeardownWorkspaceFn` that opens a PR from `branchName` into resolved `baseBranch`. Pass `{ provider: 'github' }` (`gh` on PATH). Skips when the branch was not pushed or a PR already exists; create failures are logged and do not fail the run.
55
+
56
+ ```ts
57
+ import { featureBacklog, openPrPostTeardown } from '@lumpcode/recipes';
58
+
59
+ export default featureBacklog({
60
+ configUrl: import.meta.url,
61
+ postTeardownWorkspaceFn: openPrPostTeardown({ provider: 'github' }),
62
+ });
63
+ ```
54
64
 
55
65
  ## Generic backlog stage map
56
66
 
package/dist/index.cjs CHANGED
@@ -18,6 +18,111 @@ function shellCommand(script) {
18
18
  };
19
19
  }
20
20
 
21
+ const LOG_PREFIX = '[lumpcode/recipes]';
22
+ async function openGithubPr(input) {
23
+ const { workspacePath, baseBranch, branchName, title, body } = input;
24
+ const cwd = workspacePath;
25
+ const listed = await core.execBinary({
26
+ binaryPath: 'gh',
27
+ args: ['pr', 'list', '--head', branchName, '--base', baseBranch, '--json', 'number'],
28
+ cwd,
29
+ });
30
+ if (listed.success && githubPrListHasItems(listed.data.stdout)) {
31
+ return;
32
+ }
33
+ const created = await core.execBinary({
34
+ binaryPath: 'gh',
35
+ args: [
36
+ 'pr',
37
+ 'create',
38
+ '--base',
39
+ baseBranch,
40
+ '--head',
41
+ branchName,
42
+ '--title',
43
+ title,
44
+ '--body',
45
+ body,
46
+ ],
47
+ cwd,
48
+ });
49
+ if (!created.success) {
50
+ console.error(`${LOG_PREFIX} gh pr create failed: ${created.data.message}`);
51
+ }
52
+ }
53
+ function githubPrListHasItems(stdout) {
54
+ try {
55
+ const parsed = JSON.parse(stdout);
56
+ return Array.isArray(parsed) && parsed.length > 0;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ const LUMP_BRANCH_PREFIX = 'lump/';
64
+ const OPEN_PR_PROVIDERS = ['github'];
65
+ function openPrPostTeardown(options) {
66
+ const { provider, lumpName: lumpNameOption, title, body } = options;
67
+ return async (input) => {
68
+ const { baseBranch, branchName, contextList, workspacePath } = input;
69
+ if (!branchName || branchName === baseBranch) {
70
+ return;
71
+ }
72
+ const remote = await core.execBinary({
73
+ binaryPath: 'git',
74
+ args: ['ls-remote', '--heads', 'origin', branchName],
75
+ cwd: workspacePath,
76
+ });
77
+ if (!remote.success || !remote.data.stdout.trim()) {
78
+ return;
79
+ }
80
+ const names = contextList.map((ctx) => ctx.name).filter((name) => name.length > 0);
81
+ const label = names.length > 0 ? names.join(', ') : branchName;
82
+ const lumpName = lumpNameOption ?? lumpNameFromBranch(branchName);
83
+ const resolvedTitle = title?.(input) ?? defaultPrTitle({ lumpName, label });
84
+ const resolvedBody = body?.(input) ?? `LUMP contexts: ${label}`;
85
+ await openPrWithProvider({
86
+ provider,
87
+ workspacePath,
88
+ baseBranch,
89
+ branchName,
90
+ title: resolvedTitle,
91
+ body: resolvedBody,
92
+ });
93
+ };
94
+ }
95
+ function defaultPrTitle(input) {
96
+ const { lumpName, label } = input;
97
+ if (lumpName) {
98
+ return cliUtils.getGitCommitMessage({ lumpName, contextName: label });
99
+ }
100
+ return `LUMP: ${label}`;
101
+ }
102
+ function lumpNameFromBranch(branchName) {
103
+ if (!branchName.startsWith(LUMP_BRANCH_PREFIX)) {
104
+ return undefined;
105
+ }
106
+ const rest = branchName.slice(LUMP_BRANCH_PREFIX.length);
107
+ const slash = rest.indexOf('/');
108
+ if (slash <= 0) {
109
+ return undefined;
110
+ }
111
+ return rest.slice(0, slash);
112
+ }
113
+ async function openPrWithProvider(input) {
114
+ const { provider, ...prInput } = input;
115
+ switch (provider) {
116
+ case 'github':
117
+ await openGithubPr(prInput);
118
+ return;
119
+ default: {
120
+ const _exhaustive = provider;
121
+ throw new Error(`Unhandled PR provider: ${_exhaustive}`);
122
+ }
123
+ }
124
+ }
125
+
21
126
  function normalizeMaybePromGetter(maybePromGetter, defaultValue) {
22
127
  if (typeof maybePromGetter === 'function') {
23
128
  return maybePromGetter;
@@ -1123,6 +1228,7 @@ exports.BACKLOG_STAGE_VAR = BACKLOG_STAGE_VAR;
1123
1228
  exports.BACKLOG_TASK_NAME_VAR = BACKLOG_TASK_NAME_VAR;
1124
1229
  exports.BACKLOG_TASK_VAR = BACKLOG_TASK_VAR;
1125
1230
  exports.FEATURE_BACKLOG_WORKFLOWS = FEATURE_BACKLOG_WORKFLOWS;
1231
+ exports.OPEN_PR_PROVIDERS = OPEN_PR_PROVIDERS;
1126
1232
  exports.abstractionBacklog = abstractionBacklog;
1127
1233
  exports.abstractionFinder = abstractionFinder;
1128
1234
  exports.backlog = backlog;
@@ -1136,6 +1242,7 @@ exports.getRecursiveSteps = getRecursiveSteps;
1136
1242
  exports.listTodoRelativeDirs = listTodoRelativeDirs;
1137
1243
  exports.lumpPathAndName = lumpPathAndName;
1138
1244
  exports.normalizeMaybePromGetter = normalizeMaybePromGetter;
1245
+ exports.openPrPostTeardown = openPrPostTeardown;
1139
1246
  exports.parseFeatureWorkflow = parseFeatureWorkflow;
1140
1247
  exports.projectRootFromConfigUrl = projectRootFromConfigUrl;
1141
1248
  exports.requireArtifactStep = requireArtifactStep;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,20 @@
1
1
  import { CommandDescriptor, MaybePromise, PostCommandExecFn, Step } from '@lumpcode/core';
2
- import { LumpVariables, GetContextListFnInput, GetContextListFn, StepVariables, CommandFn, LumpJsConfig, LumpJsConfigSteps, MaybePromise as MaybePromise$1, Context } from '@lumpcode/cli-utils';
2
+ import { LumpVariables, PostSetupWorkspaceFnInput, PostTeardownWorkspaceFn, GetContextListFnInput, GetContextListFn, StepVariables, CommandFn, LumpJsConfig, LumpJsConfigSteps, MaybePromise as MaybePromise$1, Context } from '@lumpcode/cli-utils';
3
3
 
4
4
  /** Run a shell script via `sh -c` (portable on Unix-like systems and Git Bash on Windows). */
5
5
  declare function shellCommand(script: string): CommandDescriptor;
6
6
 
7
+ declare const OPEN_PR_PROVIDERS: readonly ["github"];
8
+ type OpenPrProvider = (typeof OPEN_PR_PROVIDERS)[number];
9
+ type OpenPrPostTeardownOptions<V extends LumpVariables = LumpVariables> = {
10
+ provider: OpenPrProvider;
11
+ /** Overrides the lump name parsed from `lump/<lumpName>/…` branches. */
12
+ lumpName?: string;
13
+ title?: (input: PostSetupWorkspaceFnInput<V>) => string;
14
+ body?: (input: PostSetupWorkspaceFnInput<V>) => string;
15
+ };
16
+ declare function openPrPostTeardown<V extends LumpVariables = LumpVariables>(options: OpenPrPostTeardownOptions<V>): PostTeardownWorkspaceFn<V>;
17
+
7
18
  type MaybePromGetter<T, P = void> = T | ((input: P) => MaybePromise<T>);
8
19
  declare function normalizeMaybePromGetter<T, P = void>(maybePromGetter: MaybePromGetter<T, P>): (input: P) => MaybePromise<T>;
9
20
  declare function normalizeMaybePromGetter<T, P = void>(maybePromGetter: MaybePromGetter<T, P> | undefined, defaultValue: T): (input?: P) => MaybePromise<T>;
@@ -228,5 +239,5 @@ declare function resolveFeatureBacklogItem(input: {
228
239
  }): Promise<BacklogItemResolution<FeatureBacklogStage>>;
229
240
  declare const featureBacklog: <V extends LumpVariables = LumpVariables, SV extends StepVariables = StepVariables>(options: FeatureBacklogOptions<V, SV>) => LumpJsConfig<V, SV>;
230
241
 
231
- export { BACKLOG_ITEMS_DIR_VAR, BACKLOG_ITEM_DIR_VAR, BACKLOG_STAGE_VAR, BACKLOG_TASK_NAME_VAR, BACKLOG_TASK_VAR, FEATURE_BACKLOG_WORKFLOWS, abstractionBacklog, abstractionFinder, backlog, backlogRecipe, defineRecipe, ephemeralContextListFn, featureBacklog, folderBacklogContexts, folderSetTaskDoneStep, getRecursiveSteps, listTodoRelativeDirs, lumpPathAndName, normalizeMaybePromGetter, parseFeatureWorkflow, projectRootFromConfigUrl, requireArtifactStep, resolveBacklogPaths, resolveFeatureBacklogItem, resolveImplValidateCommand, retryUntilGreen, setTaskDoneStep, shellCommand, validateBaseBacklogItem, ymlBacklogContexts };
232
- export type { AbstractionBacklogOptions, AbstractionFinderOptions, AbstractionFinderScanCommand, BacklogItemResolution, BacklogOptions, BacklogPaths, BacklogStageDefinition, BaseBacklogItem, DoneBacklogItem, EphemeralContextListFnOptions, FeatureBacklogContextVariables, FeatureBacklogItem, FeatureBacklogOptions, FeatureBacklogRunnableWorkflow, FeatureBacklogStage, FeatureBacklogWorkflow, FolderBacklogContextsOptions, GetFirstStepsInput, GetRecursiveStepsOptions, ImplValidateCommand, IsValidationCommandResultOkInput, MaybePromGetter, Recipe, RetryUntilGreenInput, StepIndex, ValidationCommandFn, ValidationCommandFnInput, YmlBacklogContextsOptions };
242
+ export { BACKLOG_ITEMS_DIR_VAR, BACKLOG_ITEM_DIR_VAR, BACKLOG_STAGE_VAR, BACKLOG_TASK_NAME_VAR, BACKLOG_TASK_VAR, FEATURE_BACKLOG_WORKFLOWS, OPEN_PR_PROVIDERS, abstractionBacklog, abstractionFinder, backlog, backlogRecipe, defineRecipe, ephemeralContextListFn, featureBacklog, folderBacklogContexts, folderSetTaskDoneStep, getRecursiveSteps, listTodoRelativeDirs, lumpPathAndName, normalizeMaybePromGetter, openPrPostTeardown, parseFeatureWorkflow, projectRootFromConfigUrl, requireArtifactStep, resolveBacklogPaths, resolveFeatureBacklogItem, resolveImplValidateCommand, retryUntilGreen, setTaskDoneStep, shellCommand, validateBaseBacklogItem, ymlBacklogContexts };
243
+ export type { AbstractionBacklogOptions, AbstractionFinderOptions, AbstractionFinderScanCommand, BacklogItemResolution, BacklogOptions, BacklogPaths, BacklogStageDefinition, BaseBacklogItem, DoneBacklogItem, EphemeralContextListFnOptions, FeatureBacklogContextVariables, FeatureBacklogItem, FeatureBacklogOptions, FeatureBacklogRunnableWorkflow, FeatureBacklogStage, FeatureBacklogWorkflow, FolderBacklogContextsOptions, GetFirstStepsInput, GetRecursiveStepsOptions, ImplValidateCommand, IsValidationCommandResultOkInput, MaybePromGetter, OpenPrPostTeardownOptions, OpenPrProvider, Recipe, RetryUntilGreenInput, StepIndex, ValidationCommandFn, ValidationCommandFnInput, YmlBacklogContextsOptions };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { normalizeSteps, readYamlList, defineConfig, getContextStatus } from '@lumpcode/cli-utils';
2
- import { shellSingleQuote, pathExists } from '@lumpcode/core';
1
+ import { getGitCommitMessage, normalizeSteps, readYamlList, defineConfig, getContextStatus } from '@lumpcode/cli-utils';
2
+ import { execBinary, shellSingleQuote, pathExists } from '@lumpcode/core';
3
3
  import { fileURLToPath } from 'url';
4
4
  import path from 'path';
5
5
  import path$1 from 'node:path';
@@ -16,6 +16,111 @@ function shellCommand(script) {
16
16
  };
17
17
  }
18
18
 
19
+ const LOG_PREFIX = '[lumpcode/recipes]';
20
+ async function openGithubPr(input) {
21
+ const { workspacePath, baseBranch, branchName, title, body } = input;
22
+ const cwd = workspacePath;
23
+ const listed = await execBinary({
24
+ binaryPath: 'gh',
25
+ args: ['pr', 'list', '--head', branchName, '--base', baseBranch, '--json', 'number'],
26
+ cwd,
27
+ });
28
+ if (listed.success && githubPrListHasItems(listed.data.stdout)) {
29
+ return;
30
+ }
31
+ const created = await execBinary({
32
+ binaryPath: 'gh',
33
+ args: [
34
+ 'pr',
35
+ 'create',
36
+ '--base',
37
+ baseBranch,
38
+ '--head',
39
+ branchName,
40
+ '--title',
41
+ title,
42
+ '--body',
43
+ body,
44
+ ],
45
+ cwd,
46
+ });
47
+ if (!created.success) {
48
+ console.error(`${LOG_PREFIX} gh pr create failed: ${created.data.message}`);
49
+ }
50
+ }
51
+ function githubPrListHasItems(stdout) {
52
+ try {
53
+ const parsed = JSON.parse(stdout);
54
+ return Array.isArray(parsed) && parsed.length > 0;
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ }
60
+
61
+ const LUMP_BRANCH_PREFIX = 'lump/';
62
+ const OPEN_PR_PROVIDERS = ['github'];
63
+ function openPrPostTeardown(options) {
64
+ const { provider, lumpName: lumpNameOption, title, body } = options;
65
+ return async (input) => {
66
+ const { baseBranch, branchName, contextList, workspacePath } = input;
67
+ if (!branchName || branchName === baseBranch) {
68
+ return;
69
+ }
70
+ const remote = await execBinary({
71
+ binaryPath: 'git',
72
+ args: ['ls-remote', '--heads', 'origin', branchName],
73
+ cwd: workspacePath,
74
+ });
75
+ if (!remote.success || !remote.data.stdout.trim()) {
76
+ return;
77
+ }
78
+ const names = contextList.map((ctx) => ctx.name).filter((name) => name.length > 0);
79
+ const label = names.length > 0 ? names.join(', ') : branchName;
80
+ const lumpName = lumpNameOption ?? lumpNameFromBranch(branchName);
81
+ const resolvedTitle = title?.(input) ?? defaultPrTitle({ lumpName, label });
82
+ const resolvedBody = body?.(input) ?? `LUMP contexts: ${label}`;
83
+ await openPrWithProvider({
84
+ provider,
85
+ workspacePath,
86
+ baseBranch,
87
+ branchName,
88
+ title: resolvedTitle,
89
+ body: resolvedBody,
90
+ });
91
+ };
92
+ }
93
+ function defaultPrTitle(input) {
94
+ const { lumpName, label } = input;
95
+ if (lumpName) {
96
+ return getGitCommitMessage({ lumpName, contextName: label });
97
+ }
98
+ return `LUMP: ${label}`;
99
+ }
100
+ function lumpNameFromBranch(branchName) {
101
+ if (!branchName.startsWith(LUMP_BRANCH_PREFIX)) {
102
+ return undefined;
103
+ }
104
+ const rest = branchName.slice(LUMP_BRANCH_PREFIX.length);
105
+ const slash = rest.indexOf('/');
106
+ if (slash <= 0) {
107
+ return undefined;
108
+ }
109
+ return rest.slice(0, slash);
110
+ }
111
+ async function openPrWithProvider(input) {
112
+ const { provider, ...prInput } = input;
113
+ switch (provider) {
114
+ case 'github':
115
+ await openGithubPr(prInput);
116
+ return;
117
+ default: {
118
+ const _exhaustive = provider;
119
+ throw new Error(`Unhandled PR provider: ${_exhaustive}`);
120
+ }
121
+ }
122
+ }
123
+
19
124
  function normalizeMaybePromGetter(maybePromGetter, defaultValue) {
20
125
  if (typeof maybePromGetter === 'function') {
21
126
  return maybePromGetter;
@@ -1115,4 +1220,4 @@ Do not edit @${REQ_FILE} unless absolutely necessary.
1115
1220
  });
1116
1221
  });
1117
1222
 
1118
- export { BACKLOG_ITEMS_DIR_VAR, BACKLOG_ITEM_DIR_VAR, BACKLOG_STAGE_VAR, BACKLOG_TASK_NAME_VAR, BACKLOG_TASK_VAR, FEATURE_BACKLOG_WORKFLOWS, abstractionBacklog, abstractionFinder, backlog, backlogRecipe, defineRecipe, ephemeralContextListFn, featureBacklog, folderBacklogContexts, folderSetTaskDoneStep, getRecursiveSteps, listTodoRelativeDirs, lumpPathAndName, normalizeMaybePromGetter, parseFeatureWorkflow, projectRootFromConfigUrl, requireArtifactStep, resolveBacklogPaths, resolveFeatureBacklogItem, resolveImplValidateCommand, retryUntilGreen, setTaskDoneStep, shellCommand, validateBaseBacklogItem, ymlBacklogContexts };
1223
+ export { BACKLOG_ITEMS_DIR_VAR, BACKLOG_ITEM_DIR_VAR, BACKLOG_STAGE_VAR, BACKLOG_TASK_NAME_VAR, BACKLOG_TASK_VAR, FEATURE_BACKLOG_WORKFLOWS, OPEN_PR_PROVIDERS, abstractionBacklog, abstractionFinder, backlog, backlogRecipe, defineRecipe, ephemeralContextListFn, featureBacklog, folderBacklogContexts, folderSetTaskDoneStep, getRecursiveSteps, listTodoRelativeDirs, lumpPathAndName, normalizeMaybePromGetter, openPrPostTeardown, parseFeatureWorkflow, projectRootFromConfigUrl, requireArtifactStep, resolveBacklogPaths, resolveFeatureBacklogItem, resolveImplValidateCommand, retryUntilGreen, setTaskDoneStep, shellCommand, validateBaseBacklogItem, ymlBacklogContexts };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumpcode/recipes",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Lumpcode lump recipes and kit helpers",
5
5
  "keywords": [
6
6
  "lumpcode",
@@ -44,8 +44,8 @@
44
44
  "test:watch": "vitest watch"
45
45
  },
46
46
  "dependencies": {
47
- "@lumpcode/cli-utils": "^0.3.0",
48
- "@lumpcode/core": "^0.3.0",
47
+ "@lumpcode/cli-utils": "^0.3.1",
48
+ "@lumpcode/core": "^0.3.1",
49
49
  "js-yaml": "^5.0.0"
50
50
  },
51
51
  "devDependencies": {