@safetnsr/md-pipe 0.2.0 → 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/README.md +172 -147
- package/dist/cli.js +108 -30
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.js +110 -48
- package/dist/core/pipeline.d.ts +48 -0
- package/dist/core/pipeline.js +100 -0
- package/dist/core/run-command.d.ts +11 -0
- package/dist/core/run-command.js +86 -0
- package/dist/core/steps/copy.d.ts +10 -0
- package/dist/core/steps/copy.js +42 -0
- package/dist/core/steps/run.d.ts +13 -0
- package/dist/core/steps/run.js +46 -0
- package/dist/core/steps/template.d.ts +11 -0
- package/dist/core/steps/template.js +37 -0
- package/dist/core/steps/update-frontmatter.d.ts +6 -0
- package/dist/core/steps/update-frontmatter.js +66 -0
- package/dist/core/steps/webhook.d.ts +11 -0
- package/dist/core/steps/webhook.js +81 -0
- package/dist/core/template-vars.d.ts +43 -0
- package/dist/core/template-vars.js +143 -0
- package/dist/core/test-file.d.ts +1 -0
- package/dist/core/test-file.js +49 -21
- package/dist/core/watcher.d.ts +2 -0
- package/dist/core/watcher.js +19 -3
- package/package.json +1 -1
package/dist/core/config.js
CHANGED
|
@@ -18,6 +18,61 @@ function findConfigFile(dir) {
|
|
|
18
18
|
}
|
|
19
19
|
return null;
|
|
20
20
|
}
|
|
21
|
+
function parseTriggerMatch(raw, name) {
|
|
22
|
+
const match = {};
|
|
23
|
+
if (raw.path)
|
|
24
|
+
match.path = String(raw.path);
|
|
25
|
+
if (raw.frontmatter)
|
|
26
|
+
match.frontmatter = raw.frontmatter;
|
|
27
|
+
if (raw.frontmatter_changed) {
|
|
28
|
+
if (!Array.isArray(raw.frontmatter_changed)) {
|
|
29
|
+
throw new Error(`Config error: trigger '${name}' frontmatter_changed must be an array`);
|
|
30
|
+
}
|
|
31
|
+
match.frontmatter_changed = raw.frontmatter_changed.map(String);
|
|
32
|
+
}
|
|
33
|
+
if (raw.tags) {
|
|
34
|
+
if (!Array.isArray(raw.tags)) {
|
|
35
|
+
throw new Error(`Config error: trigger '${name}' tags must be an array`);
|
|
36
|
+
}
|
|
37
|
+
match.tags = raw.tags.map(String);
|
|
38
|
+
}
|
|
39
|
+
if (raw.content !== undefined)
|
|
40
|
+
match.content = String(raw.content);
|
|
41
|
+
if (raw.content_regex !== undefined)
|
|
42
|
+
match.content_regex = String(raw.content_regex);
|
|
43
|
+
return match;
|
|
44
|
+
}
|
|
45
|
+
function parsePipelineStep(raw, pipelineName, index) {
|
|
46
|
+
const step = {};
|
|
47
|
+
if (raw.run !== undefined)
|
|
48
|
+
step.run = String(raw.run);
|
|
49
|
+
if (raw['update-frontmatter'] !== undefined)
|
|
50
|
+
step['update-frontmatter'] = raw['update-frontmatter'];
|
|
51
|
+
if (raw.webhook !== undefined)
|
|
52
|
+
step.webhook = raw.webhook;
|
|
53
|
+
if (raw.copy !== undefined) {
|
|
54
|
+
if (typeof raw.copy === 'string') {
|
|
55
|
+
step.copy = { to: raw.copy };
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
step.copy = raw.copy;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (raw.template !== undefined)
|
|
62
|
+
step.template = raw.template;
|
|
63
|
+
if (raw.continue_on_error !== undefined)
|
|
64
|
+
step.continue_on_error = Boolean(raw.continue_on_error);
|
|
65
|
+
// Validate at least one step type is present
|
|
66
|
+
const hasType = step.run !== undefined ||
|
|
67
|
+
step['update-frontmatter'] !== undefined ||
|
|
68
|
+
step.webhook !== undefined ||
|
|
69
|
+
step.copy !== undefined ||
|
|
70
|
+
step.template !== undefined;
|
|
71
|
+
if (!hasType) {
|
|
72
|
+
throw new Error(`Config error: pipeline '${pipelineName}' step[${index}] has no recognized step type (run, update-frontmatter, webhook, copy, template)`);
|
|
73
|
+
}
|
|
74
|
+
return step;
|
|
75
|
+
}
|
|
21
76
|
function loadConfig(configPath) {
|
|
22
77
|
const raw = (0, fs_1.readFileSync)(configPath, 'utf-8');
|
|
23
78
|
const parsed = yaml_1.default.parse(raw);
|
|
@@ -27,46 +82,54 @@ function loadConfig(configPath) {
|
|
|
27
82
|
if (!parsed.watch || typeof parsed.watch !== 'string') {
|
|
28
83
|
throw new Error(`Config error: 'watch' must be a string path (e.g. "./docs")`);
|
|
29
84
|
}
|
|
30
|
-
|
|
31
|
-
|
|
85
|
+
const hasTriggers = Array.isArray(parsed.triggers) && parsed.triggers.length > 0;
|
|
86
|
+
const hasPipelines = Array.isArray(parsed.pipelines) && parsed.pipelines.length > 0;
|
|
87
|
+
if (!hasTriggers && !hasPipelines) {
|
|
88
|
+
throw new Error(`Config error: must have at least one 'triggers' entry or 'pipelines' entry`);
|
|
32
89
|
}
|
|
33
90
|
const configDir = (0, path_1.dirname)(configPath);
|
|
34
91
|
const triggers = [];
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
const match = {};
|
|
47
|
-
if (t.match.path)
|
|
48
|
-
match.path = String(t.match.path);
|
|
49
|
-
if (t.match.frontmatter)
|
|
50
|
-
match.frontmatter = t.match.frontmatter;
|
|
51
|
-
if (t.match.frontmatter_changed) {
|
|
52
|
-
if (!Array.isArray(t.match.frontmatter_changed)) {
|
|
53
|
-
throw new Error(`Config error: trigger '${t.name}' frontmatter_changed must be an array`);
|
|
92
|
+
const pipelines = [];
|
|
93
|
+
// Parse legacy triggers
|
|
94
|
+
if (hasTriggers) {
|
|
95
|
+
for (let i = 0; i < parsed.triggers.length; i++) {
|
|
96
|
+
const t = parsed.triggers[i];
|
|
97
|
+
if (!t.name || typeof t.name !== 'string') {
|
|
98
|
+
throw new Error(`Config error: trigger[${i}] must have a 'name' string`);
|
|
99
|
+
}
|
|
100
|
+
if (!t.match || typeof t.match !== 'object') {
|
|
101
|
+
throw new Error(`Config error: trigger '${t.name}' must have a 'match' object`);
|
|
54
102
|
}
|
|
55
|
-
|
|
103
|
+
if (!t.run || typeof t.run !== 'string') {
|
|
104
|
+
throw new Error(`Config error: trigger '${t.name}' must have a 'run' string`);
|
|
105
|
+
}
|
|
106
|
+
const match = parseTriggerMatch(t.match, t.name);
|
|
107
|
+
const cwd = t.cwd === 'project' ? 'project' : (t.cwd === 'file' ? 'file' : undefined);
|
|
108
|
+
triggers.push({ name: t.name, match, run: t.run, ...(cwd ? { cwd } : {}) });
|
|
56
109
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
110
|
+
}
|
|
111
|
+
// Parse pipelines
|
|
112
|
+
if (hasPipelines) {
|
|
113
|
+
for (let i = 0; i < parsed.pipelines.length; i++) {
|
|
114
|
+
const p = parsed.pipelines[i];
|
|
115
|
+
if (!p.name || typeof p.name !== 'string') {
|
|
116
|
+
throw new Error(`Config error: pipeline[${i}] must have a 'name' string`);
|
|
60
117
|
}
|
|
61
|
-
|
|
118
|
+
if (!p.trigger || typeof p.trigger !== 'object') {
|
|
119
|
+
throw new Error(`Config error: pipeline '${p.name}' must have a 'trigger' object`);
|
|
120
|
+
}
|
|
121
|
+
if (!Array.isArray(p.steps) || p.steps.length === 0) {
|
|
122
|
+
throw new Error(`Config error: pipeline '${p.name}' must have a non-empty 'steps' array`);
|
|
123
|
+
}
|
|
124
|
+
const trigger = parseTriggerMatch(p.trigger, p.name);
|
|
125
|
+
const steps = p.steps.map((s, j) => parsePipelineStep(s, p.name, j));
|
|
126
|
+
pipelines.push({
|
|
127
|
+
name: p.name,
|
|
128
|
+
trigger,
|
|
129
|
+
steps,
|
|
130
|
+
...(p.continue_on_error ? { continue_on_error: true } : {}),
|
|
131
|
+
});
|
|
62
132
|
}
|
|
63
|
-
if (t.match.content !== undefined)
|
|
64
|
-
match.content = String(t.match.content);
|
|
65
|
-
if (t.match.content_regex !== undefined)
|
|
66
|
-
match.content_regex = String(t.match.content_regex);
|
|
67
|
-
// cwd option
|
|
68
|
-
const cwd = t.cwd === 'project' ? 'project' : (t.cwd === 'file' ? 'file' : undefined);
|
|
69
|
-
triggers.push({ name: t.name, match, run: t.run, ...(cwd ? { cwd } : {}) });
|
|
70
133
|
}
|
|
71
134
|
// Resolve watch path relative to config file directory
|
|
72
135
|
const watchPath = (0, path_1.resolve)(configDir, parsed.watch);
|
|
@@ -74,7 +137,6 @@ function loadConfig(configPath) {
|
|
|
74
137
|
let debounce;
|
|
75
138
|
if (parsed.debounce !== undefined) {
|
|
76
139
|
if (typeof parsed.debounce === 'string') {
|
|
77
|
-
// Parse "500ms" or "1s"
|
|
78
140
|
const ms = parsed.debounce.match(/^(\d+)\s*ms$/i);
|
|
79
141
|
const s = parsed.debounce.match(/^(\d+)\s*s$/i);
|
|
80
142
|
if (ms)
|
|
@@ -88,7 +150,7 @@ function loadConfig(configPath) {
|
|
|
88
150
|
debounce = parsed.debounce;
|
|
89
151
|
}
|
|
90
152
|
}
|
|
91
|
-
return { watch: watchPath, configDir, triggers, ...(debounce ? { debounce } : {}) };
|
|
153
|
+
return { watch: watchPath, configDir, triggers, pipelines, ...(debounce ? { debounce } : {}) };
|
|
92
154
|
}
|
|
93
155
|
function generateDefaultConfig() {
|
|
94
156
|
return `# md-pipe configuration
|
|
@@ -96,6 +158,7 @@ function generateDefaultConfig() {
|
|
|
96
158
|
|
|
97
159
|
watch: ./docs
|
|
98
160
|
|
|
161
|
+
# Legacy triggers (simple: match + run command)
|
|
99
162
|
triggers:
|
|
100
163
|
- name: publish
|
|
101
164
|
match:
|
|
@@ -104,18 +167,17 @@ triggers:
|
|
|
104
167
|
status: publish
|
|
105
168
|
run: "echo Publishing $FILE"
|
|
106
169
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
run: "echo URGENT: $FILE needs attention"
|
|
170
|
+
# Pipelines (v0.3+): multi-step content pipelines
|
|
171
|
+
# pipelines:
|
|
172
|
+
# - name: publish-post
|
|
173
|
+
# trigger:
|
|
174
|
+
# path: "posts/**"
|
|
175
|
+
# frontmatter: { status: publish }
|
|
176
|
+
# frontmatter_changed: [status]
|
|
177
|
+
# steps:
|
|
178
|
+
# - run: "echo Publishing {{fm.title}}"
|
|
179
|
+
# - update-frontmatter: { published_at: "{{now}}", published: true }
|
|
180
|
+
# - copy: { to: "./_site/posts" }
|
|
181
|
+
# - webhook: { url: "$WEBHOOK_URL" }
|
|
120
182
|
`;
|
|
121
183
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TriggerMatch } from './config.js';
|
|
2
|
+
import { MatchResult } from './matcher.js';
|
|
3
|
+
import { type StepResult } from './steps/run.js';
|
|
4
|
+
export interface PipelineStepDef {
|
|
5
|
+
run?: string;
|
|
6
|
+
'update-frontmatter'?: Record<string, unknown>;
|
|
7
|
+
webhook?: {
|
|
8
|
+
url: string;
|
|
9
|
+
method?: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
};
|
|
13
|
+
copy?: {
|
|
14
|
+
to: string;
|
|
15
|
+
flatten?: boolean;
|
|
16
|
+
};
|
|
17
|
+
template?: {
|
|
18
|
+
src: string;
|
|
19
|
+
out: string;
|
|
20
|
+
};
|
|
21
|
+
continue_on_error?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface PipelineDef {
|
|
24
|
+
name: string;
|
|
25
|
+
trigger: TriggerMatch;
|
|
26
|
+
steps: PipelineStepDef[];
|
|
27
|
+
continue_on_error?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface PipelineResult {
|
|
30
|
+
pipelineName: string;
|
|
31
|
+
filePath: string;
|
|
32
|
+
steps: StepResult[];
|
|
33
|
+
success: boolean;
|
|
34
|
+
durationMs: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Execute a full pipeline: run steps in order, build context as we go.
|
|
38
|
+
*/
|
|
39
|
+
export declare function executePipeline(pipeline: PipelineDef, match: MatchResult, configDir: string, dryRun?: boolean): Promise<PipelineResult>;
|
|
40
|
+
/**
|
|
41
|
+
* Convert a legacy trigger (with `run` string) into a single-step pipeline.
|
|
42
|
+
* This enables backward compatibility.
|
|
43
|
+
*/
|
|
44
|
+
export declare function triggerToPipeline(trigger: {
|
|
45
|
+
name: string;
|
|
46
|
+
match: TriggerMatch;
|
|
47
|
+
run: string;
|
|
48
|
+
}): PipelineDef;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executePipeline = executePipeline;
|
|
4
|
+
exports.triggerToPipeline = triggerToPipeline;
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const template_vars_js_1 = require("./template-vars.js");
|
|
7
|
+
const run_js_1 = require("./steps/run.js");
|
|
8
|
+
const update_frontmatter_js_1 = require("./steps/update-frontmatter.js");
|
|
9
|
+
const webhook_js_1 = require("./steps/webhook.js");
|
|
10
|
+
const copy_js_1 = require("./steps/copy.js");
|
|
11
|
+
const template_js_1 = require("./steps/template.js");
|
|
12
|
+
/**
|
|
13
|
+
* Detect the step type from a step definition.
|
|
14
|
+
*/
|
|
15
|
+
function getStepType(step) {
|
|
16
|
+
if (step.run !== undefined)
|
|
17
|
+
return 'run';
|
|
18
|
+
if (step['update-frontmatter'] !== undefined)
|
|
19
|
+
return 'update-frontmatter';
|
|
20
|
+
if (step.webhook !== undefined)
|
|
21
|
+
return 'webhook';
|
|
22
|
+
if (step.copy !== undefined)
|
|
23
|
+
return 'copy';
|
|
24
|
+
if (step.template !== undefined)
|
|
25
|
+
return 'template';
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Execute a single pipeline step.
|
|
30
|
+
*/
|
|
31
|
+
async function executeStep(step, ctx, configDir, dryRun) {
|
|
32
|
+
const type = getStepType(step);
|
|
33
|
+
switch (type) {
|
|
34
|
+
case 'run':
|
|
35
|
+
return (0, run_js_1.executeRunStep)({ run: step.run }, ctx, (0, path_1.dirname)(ctx.file), dryRun);
|
|
36
|
+
case 'update-frontmatter':
|
|
37
|
+
return (0, update_frontmatter_js_1.executeUpdateFrontmatterStep)({ 'update-frontmatter': step['update-frontmatter'] }, ctx, dryRun);
|
|
38
|
+
case 'webhook':
|
|
39
|
+
return await (0, webhook_js_1.executeWebhookStep)({ webhook: step.webhook }, ctx, dryRun);
|
|
40
|
+
case 'copy':
|
|
41
|
+
return (0, copy_js_1.executeCopyStep)({ copy: step.copy }, ctx, configDir, dryRun);
|
|
42
|
+
case 'template':
|
|
43
|
+
return (0, template_js_1.executeTemplateStep)({ template: step.template }, ctx, configDir, dryRun);
|
|
44
|
+
default:
|
|
45
|
+
return {
|
|
46
|
+
type: 'unknown',
|
|
47
|
+
success: false,
|
|
48
|
+
stdout: '',
|
|
49
|
+
stderr: `Unknown step type in: ${JSON.stringify(step)}`,
|
|
50
|
+
exitCode: 1,
|
|
51
|
+
durationMs: 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Execute a full pipeline: run steps in order, build context as we go.
|
|
57
|
+
*/
|
|
58
|
+
async function executePipeline(pipeline, match, configDir, dryRun = false) {
|
|
59
|
+
const { file, diff } = match;
|
|
60
|
+
const ctx = (0, template_vars_js_1.buildContext)(file.filePath, file.relativePath, (0, path_1.dirname)(file.filePath), { ...file.frontmatter }, [...file.tags], file.content, file.body, diff);
|
|
61
|
+
const stepResults = [];
|
|
62
|
+
const pipelineStart = Date.now();
|
|
63
|
+
let allSuccess = true;
|
|
64
|
+
for (const step of pipeline.steps) {
|
|
65
|
+
const result = await executeStep(step, ctx, configDir, dryRun);
|
|
66
|
+
stepResults.push(result);
|
|
67
|
+
// Add step output to context for subsequent steps
|
|
68
|
+
const stepOutput = {
|
|
69
|
+
stdout: result.stdout,
|
|
70
|
+
stderr: result.stderr,
|
|
71
|
+
exitCode: result.exitCode,
|
|
72
|
+
};
|
|
73
|
+
ctx.steps.push(stepOutput);
|
|
74
|
+
if (!result.success) {
|
|
75
|
+
allSuccess = false;
|
|
76
|
+
const continueOnError = step.continue_on_error ?? pipeline.continue_on_error ?? false;
|
|
77
|
+
if (!continueOnError) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
pipelineName: pipeline.name,
|
|
84
|
+
filePath: file.filePath,
|
|
85
|
+
steps: stepResults,
|
|
86
|
+
success: allSuccess,
|
|
87
|
+
durationMs: Date.now() - pipelineStart,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Convert a legacy trigger (with `run` string) into a single-step pipeline.
|
|
92
|
+
* This enables backward compatibility.
|
|
93
|
+
*/
|
|
94
|
+
function triggerToPipeline(trigger) {
|
|
95
|
+
return {
|
|
96
|
+
name: trigger.name,
|
|
97
|
+
trigger: trigger.match,
|
|
98
|
+
steps: [{ run: trigger.run }],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MdPipeConfig } from './config.js';
|
|
2
|
+
import { PipelineResult } from './pipeline.js';
|
|
3
|
+
export interface RunCommandResult {
|
|
4
|
+
success: boolean;
|
|
5
|
+
results: PipelineResult[];
|
|
6
|
+
errors: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Run a named pipeline manually on a specific file or all matching files.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runPipelineCommand(config: MdPipeConfig, pipelineName: string, filePath?: string, dryRun?: boolean): Promise<RunCommandResult>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runPipelineCommand = runPipelineCommand;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const matcher_js_1 = require("./matcher.js");
|
|
7
|
+
const pipeline_js_1 = require("./pipeline.js");
|
|
8
|
+
function findMarkdownFiles(dir) {
|
|
9
|
+
const results = [];
|
|
10
|
+
function walk(current) {
|
|
11
|
+
const entries = (0, fs_1.readdirSync)(current, { withFileTypes: true });
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const full = (0, path_1.resolve)(current, entry.name);
|
|
14
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
15
|
+
walk(full);
|
|
16
|
+
}
|
|
17
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
18
|
+
results.push(full);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
walk(dir);
|
|
23
|
+
return results;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run a named pipeline manually on a specific file or all matching files.
|
|
27
|
+
*/
|
|
28
|
+
async function runPipelineCommand(config, pipelineName, filePath, dryRun = false) {
|
|
29
|
+
// Find the pipeline (check both pipelines and legacy triggers)
|
|
30
|
+
let pipeline;
|
|
31
|
+
pipeline = config.pipelines.find(p => p.name === pipelineName);
|
|
32
|
+
if (!pipeline) {
|
|
33
|
+
const trigger = config.triggers.find(t => t.name === pipelineName);
|
|
34
|
+
if (trigger) {
|
|
35
|
+
pipeline = (0, pipeline_js_1.triggerToPipeline)(trigger);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!pipeline) {
|
|
39
|
+
const available = [
|
|
40
|
+
...config.pipelines.map(p => p.name),
|
|
41
|
+
...config.triggers.map(t => t.name),
|
|
42
|
+
];
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
results: [],
|
|
46
|
+
errors: [`Pipeline '${pipelineName}' not found. Available: ${available.join(', ') || 'none'}`],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const results = [];
|
|
50
|
+
const errors = [];
|
|
51
|
+
if (filePath) {
|
|
52
|
+
// Run on specific file
|
|
53
|
+
const absPath = (0, path_1.resolve)(filePath);
|
|
54
|
+
const relPath = (0, path_1.relative)(config.watch, absPath);
|
|
55
|
+
try {
|
|
56
|
+
const file = (0, matcher_js_1.parseMarkdownFile)(absPath, relPath);
|
|
57
|
+
const match = { trigger: { name: pipeline.name, match: pipeline.trigger, run: '' }, file, diff: null };
|
|
58
|
+
const result = await (0, pipeline_js_1.executePipeline)(pipeline, match, config.configDir, dryRun);
|
|
59
|
+
results.push(result);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
errors.push(`Error processing ${filePath}: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Run on all matching files
|
|
67
|
+
const files = findMarkdownFiles(config.watch);
|
|
68
|
+
for (const fp of files) {
|
|
69
|
+
const relPath = (0, path_1.relative)(config.watch, fp);
|
|
70
|
+
try {
|
|
71
|
+
const file = (0, matcher_js_1.parseMarkdownFile)(fp, relPath);
|
|
72
|
+
const triggerDef = { name: pipeline.name, match: pipeline.trigger, run: '' };
|
|
73
|
+
const match = (0, matcher_js_1.evaluateTrigger)(triggerDef, file);
|
|
74
|
+
if (match) {
|
|
75
|
+
const result = await (0, pipeline_js_1.executePipeline)(pipeline, match, config.configDir, dryRun);
|
|
76
|
+
results.push(result);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
errors.push(`Error processing ${fp}: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const success = errors.length === 0 && results.every(r => r.success);
|
|
85
|
+
return { success, results, errors };
|
|
86
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TemplateContext } from '../template-vars.js';
|
|
2
|
+
import type { StepResult } from './run.js';
|
|
3
|
+
export interface CopyStepConfig {
|
|
4
|
+
copy: {
|
|
5
|
+
to: string;
|
|
6
|
+
/** If true, preserve directory structure relative to watch dir. Default: false */
|
|
7
|
+
flatten?: boolean;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function executeCopyStep(config: CopyStepConfig, ctx: TemplateContext, configDir: string, dryRun: boolean): StepResult;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeCopyStep = executeCopyStep;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const template_vars_js_1 = require("../template-vars.js");
|
|
7
|
+
function executeCopyStep(config, ctx, configDir, dryRun) {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
try {
|
|
10
|
+
const destDir = (0, path_1.resolve)(configDir, (0, template_vars_js_1.expandTemplate)(config.copy.to, ctx));
|
|
11
|
+
const flatten = config.copy.flatten ?? false;
|
|
12
|
+
let destPath;
|
|
13
|
+
if (flatten) {
|
|
14
|
+
destPath = (0, path_1.join)(destDir, ctx.basename);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
destPath = (0, path_1.join)(destDir, ctx.relative);
|
|
18
|
+
}
|
|
19
|
+
if (!dryRun) {
|
|
20
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(destPath), { recursive: true });
|
|
21
|
+
(0, fs_1.copyFileSync)(ctx.file, destPath);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
type: 'copy',
|
|
25
|
+
success: true,
|
|
26
|
+
stdout: `Copied to ${destPath}${dryRun ? ' [dry run]' : ''}`,
|
|
27
|
+
stderr: '',
|
|
28
|
+
exitCode: 0,
|
|
29
|
+
durationMs: Date.now() - start,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
return {
|
|
34
|
+
type: 'copy',
|
|
35
|
+
success: false,
|
|
36
|
+
stdout: '',
|
|
37
|
+
stderr: err.message || String(err),
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
durationMs: Date.now() - start,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TemplateContext } from '../template-vars.js';
|
|
2
|
+
export interface RunStepConfig {
|
|
3
|
+
run: string;
|
|
4
|
+
}
|
|
5
|
+
export interface StepResult {
|
|
6
|
+
type: string;
|
|
7
|
+
success: boolean;
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
exitCode: number;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function executeRunStep(config: RunStepConfig, ctx: TemplateContext, cwd: string, dryRun: boolean): StepResult;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeRunStep = executeRunStep;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const template_vars_js_1 = require("../template-vars.js");
|
|
6
|
+
function executeRunStep(config, ctx, cwd, dryRun) {
|
|
7
|
+
const command = (0, template_vars_js_1.expandTemplate)(config.run, ctx);
|
|
8
|
+
if (dryRun) {
|
|
9
|
+
return {
|
|
10
|
+
type: 'run',
|
|
11
|
+
success: true,
|
|
12
|
+
stdout: '[dry run]',
|
|
13
|
+
stderr: '',
|
|
14
|
+
exitCode: 0,
|
|
15
|
+
durationMs: 0,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const env = (0, template_vars_js_1.buildEnvVars)(ctx);
|
|
19
|
+
const start = Date.now();
|
|
20
|
+
try {
|
|
21
|
+
const stdout = (0, child_process_1.execSync)(command, {
|
|
22
|
+
cwd,
|
|
23
|
+
timeout: 30000,
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
env: { ...process.env, ...env },
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
type: 'run',
|
|
29
|
+
success: true,
|
|
30
|
+
stdout: stdout.trim(),
|
|
31
|
+
stderr: '',
|
|
32
|
+
exitCode: 0,
|
|
33
|
+
durationMs: Date.now() - start,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
return {
|
|
38
|
+
type: 'run',
|
|
39
|
+
success: false,
|
|
40
|
+
stdout: err.stdout?.trim() ?? '',
|
|
41
|
+
stderr: err.stderr?.trim() ?? '',
|
|
42
|
+
exitCode: err.status ?? 1,
|
|
43
|
+
durationMs: Date.now() - start,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TemplateContext } from '../template-vars.js';
|
|
2
|
+
import type { StepResult } from './run.js';
|
|
3
|
+
export interface TemplateStepConfig {
|
|
4
|
+
template: {
|
|
5
|
+
/** Path to template file (relative to config dir) */
|
|
6
|
+
src: string;
|
|
7
|
+
/** Output path (relative to config dir). Supports template vars. */
|
|
8
|
+
out: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare function executeTemplateStep(config: TemplateStepConfig, ctx: TemplateContext, configDir: string, dryRun: boolean): StepResult;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeTemplateStep = executeTemplateStep;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const template_vars_js_1 = require("../template-vars.js");
|
|
7
|
+
function executeTemplateStep(config, ctx, configDir, dryRun) {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
try {
|
|
10
|
+
const srcPath = (0, path_1.resolve)(configDir, (0, template_vars_js_1.expandTemplate)(config.template.src, ctx));
|
|
11
|
+
const outPath = (0, path_1.resolve)(configDir, (0, template_vars_js_1.expandTemplate)(config.template.out, ctx));
|
|
12
|
+
const templateContent = (0, fs_1.readFileSync)(srcPath, 'utf-8');
|
|
13
|
+
const rendered = (0, template_vars_js_1.expandTemplate)(templateContent, ctx);
|
|
14
|
+
if (!dryRun) {
|
|
15
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(outPath), { recursive: true });
|
|
16
|
+
(0, fs_1.writeFileSync)(outPath, rendered, 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
type: 'template',
|
|
20
|
+
success: true,
|
|
21
|
+
stdout: `Rendered ${srcPath} → ${outPath}${dryRun ? ' [dry run]' : ''}`,
|
|
22
|
+
stderr: '',
|
|
23
|
+
exitCode: 0,
|
|
24
|
+
durationMs: Date.now() - start,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
return {
|
|
29
|
+
type: 'template',
|
|
30
|
+
success: false,
|
|
31
|
+
stdout: '',
|
|
32
|
+
stderr: err.message || String(err),
|
|
33
|
+
exitCode: 1,
|
|
34
|
+
durationMs: Date.now() - start,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TemplateContext } from '../template-vars.js';
|
|
2
|
+
import type { StepResult } from './run.js';
|
|
3
|
+
export interface UpdateFrontmatterStepConfig {
|
|
4
|
+
'update-frontmatter': Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
export declare function executeUpdateFrontmatterStep(config: UpdateFrontmatterStepConfig, ctx: TemplateContext, dryRun: boolean): StepResult;
|