fraim-framework 2.0.34 → 2.0.35
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/dist/tests/test-markdown-to-pdf.js +454 -0
- package/dist/tests/test-prep-issue.js +34 -1
- package/package.json +4 -1
- package/registry/scripts/markdown-to-pdf.js +391 -0
- package/registry/scripts/prep-issue.sh +61 -30
- package/registry/templates/marketing/STORYTELLING-TEMPLATE.md +130 -0
- package/registry/workflows/convert-to-pdf.md +235 -0
- package/registry/workflows/marketing/storytelling.md +65 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Markdown to PDF Converter Script
|
|
5
|
+
*
|
|
6
|
+
* This script converts markdown files to PDF using puppeteer and markdown-it.
|
|
7
|
+
* It supports various markdown features including tables, code blocks, and images.
|
|
8
|
+
*
|
|
9
|
+
* EXECUTION MODEL:
|
|
10
|
+
* - Script location: ~/.fraim/scripts/markdown-to-pdf.js
|
|
11
|
+
* - Working directory: Current project directory (process.cwd())
|
|
12
|
+
* - Input/output files: Relative to current project directory
|
|
13
|
+
* - Config: Reads from .fraim/config.json in current project directory
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node ~/.fraim/scripts/markdown-to-pdf.js <input.md> [output.pdf] [options]
|
|
17
|
+
*
|
|
18
|
+
* Options:
|
|
19
|
+
* --format <format> Paper format (A4, Letter, Legal, etc.) - default: A4
|
|
20
|
+
* --margin <margin> Page margins in inches - default: 0.5
|
|
21
|
+
* --css <file> Custom CSS file for styling
|
|
22
|
+
* --header <text> Header text
|
|
23
|
+
* --footer <text> Footer text
|
|
24
|
+
* --landscape Use landscape orientation
|
|
25
|
+
* --no-background Disable background graphics
|
|
26
|
+
*
|
|
27
|
+
* Examples:
|
|
28
|
+
* node ~/.fraim/scripts/markdown-to-pdf.js README.md
|
|
29
|
+
* node ~/.fraim/scripts/markdown-to-pdf.js docs/spec.md output/spec.pdf --format Letter
|
|
30
|
+
* node ~/.fraim/scripts/markdown-to-pdf.js report.md --css custom.css --header "Company Report"
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const fs = require('fs');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
|
|
36
|
+
// Get project directory (where the user is working)
|
|
37
|
+
const PROJECT_DIR = process.cwd();
|
|
38
|
+
const CONFIG_FILE = path.join(PROJECT_DIR, '.fraim', 'config.json');
|
|
39
|
+
|
|
40
|
+
// Load project configuration if available
|
|
41
|
+
function loadProjectConfig() {
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
44
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
45
|
+
return config;
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn('Warning: Could not load project config from .fraim/config.json');
|
|
49
|
+
}
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check if required dependencies are available
|
|
54
|
+
function checkDependencies() {
|
|
55
|
+
const requiredPackages = ['puppeteer', 'markdown-it', 'markdown-it-highlightjs'];
|
|
56
|
+
const missingPackages = [];
|
|
57
|
+
|
|
58
|
+
for (const pkg of requiredPackages) {
|
|
59
|
+
try {
|
|
60
|
+
require.resolve(pkg);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
missingPackages.push(pkg);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (missingPackages.length > 0) {
|
|
67
|
+
console.error('Missing required packages:', missingPackages.join(', '));
|
|
68
|
+
console.error('Install them with: npm install', missingPackages.join(' '));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Parse command line arguments
|
|
74
|
+
function parseArgs() {
|
|
75
|
+
const args = process.argv.slice(2);
|
|
76
|
+
|
|
77
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
78
|
+
console.log(`
|
|
79
|
+
Markdown to PDF Converter
|
|
80
|
+
|
|
81
|
+
Usage: node ~/.fraim/scripts/markdown-to-pdf.js <input.md> [output.pdf] [options]
|
|
82
|
+
|
|
83
|
+
Options:
|
|
84
|
+
--format <format> Paper format (A4, Letter, Legal, etc.) - default: A4
|
|
85
|
+
--margin <margin> Page margins in inches - default: 0.5
|
|
86
|
+
--css <file> Custom CSS file for styling
|
|
87
|
+
--header <text> Header text
|
|
88
|
+
--footer <text> Footer text
|
|
89
|
+
--landscape Use landscape orientation
|
|
90
|
+
--no-background Disable background graphics
|
|
91
|
+
--help, -h Show this help message
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
node ~/.fraim/scripts/markdown-to-pdf.js README.md
|
|
95
|
+
node ~/.fraim/scripts/markdown-to-pdf.js docs/spec.md output/spec.pdf --format Letter
|
|
96
|
+
node ~/.fraim/scripts/markdown-to-pdf.js report.md --css custom.css --header "Company Report"
|
|
97
|
+
|
|
98
|
+
Working Directory: ${PROJECT_DIR}
|
|
99
|
+
Config File: ${CONFIG_FILE}
|
|
100
|
+
`);
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const config = {
|
|
105
|
+
input: args[0],
|
|
106
|
+
output: null,
|
|
107
|
+
format: 'A4',
|
|
108
|
+
margin: '0.5in',
|
|
109
|
+
css: null,
|
|
110
|
+
header: null,
|
|
111
|
+
footer: null,
|
|
112
|
+
landscape: false,
|
|
113
|
+
background: true
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Resolve input path relative to project directory
|
|
117
|
+
if (!path.isAbsolute(config.input)) {
|
|
118
|
+
config.input = path.resolve(PROJECT_DIR, config.input);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if second argument is output file or option
|
|
122
|
+
if (args[1] && !args[1].startsWith('--')) {
|
|
123
|
+
config.output = args[1];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Parse options
|
|
127
|
+
for (let i = 0; i < args.length; i++) {
|
|
128
|
+
const arg = args[i];
|
|
129
|
+
|
|
130
|
+
if (arg === '--format' && args[i + 1]) {
|
|
131
|
+
config.format = args[i + 1];
|
|
132
|
+
i++;
|
|
133
|
+
} else if (arg === '--margin' && args[i + 1]) {
|
|
134
|
+
config.margin = args[i + 1];
|
|
135
|
+
i++;
|
|
136
|
+
} else if (arg === '--css' && args[i + 1]) {
|
|
137
|
+
config.css = args[i + 1];
|
|
138
|
+
i++;
|
|
139
|
+
} else if (arg === '--header' && args[i + 1]) {
|
|
140
|
+
config.header = args[i + 1];
|
|
141
|
+
i++;
|
|
142
|
+
} else if (arg === '--footer' && args[i + 1]) {
|
|
143
|
+
config.footer = args[i + 1];
|
|
144
|
+
i++;
|
|
145
|
+
} else if (arg === '--landscape') {
|
|
146
|
+
config.landscape = true;
|
|
147
|
+
} else if (arg === '--no-background') {
|
|
148
|
+
config.background = false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Set default output if not provided
|
|
153
|
+
if (!config.output) {
|
|
154
|
+
const inputPath = path.parse(config.input);
|
|
155
|
+
config.output = path.join(inputPath.dir, inputPath.name + '.pdf');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Resolve output path relative to project directory
|
|
159
|
+
if (!path.isAbsolute(config.output)) {
|
|
160
|
+
config.output = path.resolve(PROJECT_DIR, config.output);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Resolve CSS path relative to project directory if provided
|
|
164
|
+
if (config.css && !path.isAbsolute(config.css)) {
|
|
165
|
+
config.css = path.resolve(PROJECT_DIR, config.css);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return config;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Convert markdown to HTML
|
|
172
|
+
async function markdownToHtml(markdownContent, customCss = null) {
|
|
173
|
+
const MarkdownIt = require('markdown-it');
|
|
174
|
+
const hljs = require('markdown-it-highlightjs');
|
|
175
|
+
|
|
176
|
+
const md = new MarkdownIt({
|
|
177
|
+
html: true,
|
|
178
|
+
linkify: true,
|
|
179
|
+
typographer: true,
|
|
180
|
+
breaks: false
|
|
181
|
+
}).use(hljs);
|
|
182
|
+
|
|
183
|
+
const htmlContent = md.render(markdownContent);
|
|
184
|
+
|
|
185
|
+
// Default CSS for better PDF rendering
|
|
186
|
+
const defaultCss = `
|
|
187
|
+
body {
|
|
188
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
189
|
+
line-height: 1.6;
|
|
190
|
+
color: #333;
|
|
191
|
+
max-width: none;
|
|
192
|
+
margin: 0;
|
|
193
|
+
padding: 20px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
h1, h2, h3, h4, h5, h6 {
|
|
197
|
+
color: #2c3e50;
|
|
198
|
+
margin-top: 2em;
|
|
199
|
+
margin-bottom: 1em;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
h1 { font-size: 2.5em; border-bottom: 2px solid #3498db; padding-bottom: 0.3em; }
|
|
203
|
+
h2 { font-size: 2em; border-bottom: 1px solid #bdc3c7; padding-bottom: 0.3em; }
|
|
204
|
+
h3 { font-size: 1.5em; }
|
|
205
|
+
|
|
206
|
+
code {
|
|
207
|
+
background-color: #f8f9fa;
|
|
208
|
+
padding: 2px 4px;
|
|
209
|
+
border-radius: 3px;
|
|
210
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
pre {
|
|
214
|
+
background-color: #f8f9fa;
|
|
215
|
+
border: 1px solid #e9ecef;
|
|
216
|
+
border-radius: 6px;
|
|
217
|
+
padding: 16px;
|
|
218
|
+
overflow-x: auto;
|
|
219
|
+
margin: 1em 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
pre code {
|
|
223
|
+
background: none;
|
|
224
|
+
padding: 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
table {
|
|
228
|
+
border-collapse: collapse;
|
|
229
|
+
width: 100%;
|
|
230
|
+
margin: 1em 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
th, td {
|
|
234
|
+
border: 1px solid #ddd;
|
|
235
|
+
padding: 12px;
|
|
236
|
+
text-align: left;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
th {
|
|
240
|
+
background-color: #f2f2f2;
|
|
241
|
+
font-weight: bold;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
blockquote {
|
|
245
|
+
border-left: 4px solid #3498db;
|
|
246
|
+
margin: 1em 0;
|
|
247
|
+
padding-left: 1em;
|
|
248
|
+
color: #7f8c8d;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
img {
|
|
252
|
+
max-width: 100%;
|
|
253
|
+
height: auto;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
a {
|
|
257
|
+
color: #3498db;
|
|
258
|
+
text-decoration: none;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
a:hover {
|
|
262
|
+
text-decoration: underline;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
ul, ol {
|
|
266
|
+
padding-left: 2em;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
li {
|
|
270
|
+
margin: 0.5em 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@media print {
|
|
274
|
+
body { margin: 0; }
|
|
275
|
+
h1, h2, h3, h4, h5, h6 { page-break-after: avoid; }
|
|
276
|
+
pre, blockquote { page-break-inside: avoid; }
|
|
277
|
+
img { page-break-inside: avoid; }
|
|
278
|
+
}
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
let customCssContent = '';
|
|
282
|
+
if (customCss && fs.existsSync(customCss)) {
|
|
283
|
+
customCssContent = fs.readFileSync(customCss, 'utf8');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return `
|
|
287
|
+
<!DOCTYPE html>
|
|
288
|
+
<html>
|
|
289
|
+
<head>
|
|
290
|
+
<meta charset="utf-8">
|
|
291
|
+
<title>Converted from Markdown</title>
|
|
292
|
+
<style>${defaultCss}${customCssContent}</style>
|
|
293
|
+
</head>
|
|
294
|
+
<body>
|
|
295
|
+
${htmlContent}
|
|
296
|
+
</body>
|
|
297
|
+
</html>
|
|
298
|
+
`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Convert HTML to PDF using Puppeteer
|
|
302
|
+
async function htmlToPdf(html, outputPath, config) {
|
|
303
|
+
const puppeteer = require('puppeteer');
|
|
304
|
+
|
|
305
|
+
const browser = await puppeteer.launch({
|
|
306
|
+
headless: 'new',
|
|
307
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const page = await browser.newPage();
|
|
312
|
+
await page.setContent(html, { waitUntil: 'networkidle0' });
|
|
313
|
+
|
|
314
|
+
const pdfOptions = {
|
|
315
|
+
path: outputPath,
|
|
316
|
+
format: config.format,
|
|
317
|
+
margin: {
|
|
318
|
+
top: config.margin,
|
|
319
|
+
right: config.margin,
|
|
320
|
+
bottom: config.margin,
|
|
321
|
+
left: config.margin
|
|
322
|
+
},
|
|
323
|
+
landscape: config.landscape,
|
|
324
|
+
printBackground: config.background,
|
|
325
|
+
preferCSSPageSize: true
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (config.header) {
|
|
329
|
+
pdfOptions.displayHeaderFooter = true;
|
|
330
|
+
pdfOptions.headerTemplate = `<div style="font-size: 10px; width: 100%; text-align: center;">${config.header}</div>`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (config.footer) {
|
|
334
|
+
pdfOptions.displayHeaderFooter = true;
|
|
335
|
+
pdfOptions.footerTemplate = `<div style="font-size: 10px; width: 100%; text-align: center;">${config.footer}</div>`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
await page.pdf(pdfOptions);
|
|
339
|
+
console.log(`✅ PDF generated successfully: ${path.relative(PROJECT_DIR, outputPath)}`);
|
|
340
|
+
|
|
341
|
+
} finally {
|
|
342
|
+
await browser.close();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Main function
|
|
347
|
+
async function main() {
|
|
348
|
+
try {
|
|
349
|
+
checkDependencies();
|
|
350
|
+
const config = parseArgs();
|
|
351
|
+
const projectConfig = loadProjectConfig();
|
|
352
|
+
|
|
353
|
+
console.log(`📄 Converting markdown to PDF...`);
|
|
354
|
+
console.log(` Working directory: ${PROJECT_DIR}`);
|
|
355
|
+
console.log(` Input: ${path.relative(PROJECT_DIR, config.input)}`);
|
|
356
|
+
console.log(` Output: ${path.relative(PROJECT_DIR, config.output)}`);
|
|
357
|
+
|
|
358
|
+
// Validate input file
|
|
359
|
+
if (!fs.existsSync(config.input)) {
|
|
360
|
+
console.error(`❌ Input file not found: ${path.relative(PROJECT_DIR, config.input)}`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Ensure output directory exists
|
|
365
|
+
const outputDir = path.dirname(config.output);
|
|
366
|
+
if (!fs.existsSync(outputDir)) {
|
|
367
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
368
|
+
console.log(`📁 Created output directory: ${path.relative(PROJECT_DIR, outputDir)}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Read markdown file
|
|
372
|
+
const markdownContent = fs.readFileSync(config.input, 'utf8');
|
|
373
|
+
|
|
374
|
+
// Convert to HTML
|
|
375
|
+
const html = await markdownToHtml(markdownContent, config.css);
|
|
376
|
+
|
|
377
|
+
// Convert to PDF
|
|
378
|
+
await htmlToPdf(html, config.output, config);
|
|
379
|
+
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error('❌ Error:', error.message);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Run if called directly
|
|
387
|
+
if (require.main === module) {
|
|
388
|
+
main();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
module.exports = { markdownToHtml, htmlToPdf, parseArgs };
|
|
@@ -134,6 +134,7 @@ try {
|
|
|
134
134
|
|
|
135
135
|
// Support both 'repository' (new) and 'git' (legacy/current) schemas
|
|
136
136
|
let repo = config.repository;
|
|
137
|
+
let defaultBranch = 'master'; // Default fallback
|
|
137
138
|
|
|
138
139
|
if (!repo) {
|
|
139
140
|
if (config.git) {
|
|
@@ -142,13 +143,18 @@ try {
|
|
|
142
143
|
name: config.git.repoName,
|
|
143
144
|
url: config.git.repoUrl || \`https://github.com/\${config.git.repoOwner}/\${config.git.repoName}.git\`
|
|
144
145
|
};
|
|
146
|
+
// Extract defaultBranch from git config
|
|
147
|
+
defaultBranch = config.git.defaultBranch || 'master';
|
|
145
148
|
}
|
|
149
|
+
} else {
|
|
150
|
+
// Extract defaultBranch from repository config
|
|
151
|
+
defaultBranch = repo.defaultBranch || 'master';
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
if (!repo || !repo.owner || !repo.name || !repo.url) {
|
|
149
155
|
process.exit(1);
|
|
150
156
|
}
|
|
151
|
-
console.log(\`\${repo.owner}
|
|
157
|
+
console.log(\`\${repo.owner}|\${repo.name}|\${repo.url}|\${defaultBranch}\`);
|
|
152
158
|
} catch (e) {
|
|
153
159
|
process.exit(1);
|
|
154
160
|
}
|
|
@@ -161,13 +167,14 @@ if [ $? -ne 0 ]; then
|
|
|
161
167
|
exit 1
|
|
162
168
|
fi
|
|
163
169
|
|
|
164
|
-
# Split the result into variables
|
|
165
|
-
IFS='
|
|
170
|
+
# Split the result into variables using pipe delimiter
|
|
171
|
+
IFS='|' read -r REPO_OWNER REPO_NAME REPO_URL CONFIG_DEFAULT_BRANCH <<< "$REPO_INFO"
|
|
166
172
|
|
|
167
173
|
echo "Repository Configuration:"
|
|
168
174
|
echo " Owner: $REPO_OWNER"
|
|
169
175
|
echo " Name: $REPO_NAME"
|
|
170
176
|
echo " URL: $REPO_URL"
|
|
177
|
+
echo " Default Branch: $CONFIG_DEFAULT_BRANCH"
|
|
171
178
|
echo
|
|
172
179
|
|
|
173
180
|
echo "=== $REPO_NAME - Issue Preparation ==="
|
|
@@ -195,45 +202,69 @@ echo "Step 1: Determining base branch from current repository..."
|
|
|
195
202
|
|
|
196
203
|
# Determine base branch from the ORIGINAL repository (before cloning)
|
|
197
204
|
if [ "$USE_DEFAULT_BRANCH" = true ]; then
|
|
198
|
-
#
|
|
199
|
-
DEFAULT_BRANCH=""
|
|
200
|
-
|
|
201
|
-
if git ls-remote --exit-code --heads "$REPO_URL" "$candidate" >/dev/null 2>&1; then
|
|
202
|
-
DEFAULT_BRANCH="$candidate"
|
|
203
|
-
echo "✓ Detected default branch: $DEFAULT_BRANCH"
|
|
204
|
-
break
|
|
205
|
-
fi
|
|
206
|
-
done
|
|
207
|
-
|
|
208
|
-
# Fallback to master if nothing else worked
|
|
209
|
-
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
210
|
-
DEFAULT_BRANCH="master"
|
|
211
|
-
echo "⚠️ Could not detect default branch, defaulting to: $DEFAULT_BRANCH"
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
BASE_BRANCH="$DEFAULT_BRANCH"
|
|
215
|
-
echo "Using detected default branch as base: $BASE_BRANCH (--use-default flag)"
|
|
216
|
-
else
|
|
217
|
-
# Get current branch from the original repo
|
|
218
|
-
ORIGINAL_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
|
205
|
+
# Use the default branch from FRAIM config
|
|
206
|
+
DEFAULT_BRANCH="$CONFIG_DEFAULT_BRANCH"
|
|
207
|
+
echo "✓ Using default branch from FRAIM config: $DEFAULT_BRANCH"
|
|
219
208
|
|
|
220
|
-
|
|
221
|
-
|
|
209
|
+
# Verify the branch exists on remote
|
|
210
|
+
if git ls-remote --exit-code --heads "$REPO_URL" "$DEFAULT_BRANCH" >/dev/null 2>&1; then
|
|
211
|
+
echo "✓ Confirmed branch '$DEFAULT_BRANCH' exists on remote"
|
|
212
|
+
else
|
|
213
|
+
echo "⚠️ Warning: Configured default branch '$DEFAULT_BRANCH' not found on remote"
|
|
214
|
+
echo " Falling back to branch detection..."
|
|
222
215
|
|
|
223
|
-
#
|
|
216
|
+
# Fallback to detection if configured branch doesn't exist
|
|
224
217
|
DEFAULT_BRANCH=""
|
|
225
218
|
for candidate in main master develop; do
|
|
226
219
|
if git ls-remote --exit-code --heads "$REPO_URL" "$candidate" >/dev/null 2>&1; then
|
|
227
220
|
DEFAULT_BRANCH="$candidate"
|
|
228
|
-
echo "✓ Detected
|
|
221
|
+
echo "✓ Detected fallback branch: $DEFAULT_BRANCH"
|
|
229
222
|
break
|
|
230
223
|
fi
|
|
231
224
|
done
|
|
232
225
|
|
|
233
|
-
#
|
|
226
|
+
# Final fallback to master
|
|
234
227
|
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
235
228
|
DEFAULT_BRANCH="master"
|
|
236
|
-
echo "⚠️ Could not detect
|
|
229
|
+
echo "⚠️ Could not detect any branch, defaulting to: $DEFAULT_BRANCH"
|
|
230
|
+
fi
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
BASE_BRANCH="$DEFAULT_BRANCH"
|
|
234
|
+
echo "Using default branch as base: $BASE_BRANCH (--use-default flag)"
|
|
235
|
+
else
|
|
236
|
+
# Get current branch from the original repo
|
|
237
|
+
ORIGINAL_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
|
238
|
+
|
|
239
|
+
if [ -z "$ORIGINAL_BRANCH" ]; then
|
|
240
|
+
echo "Warning: Detached HEAD detected in original repo, falling back to configured default branch"
|
|
241
|
+
|
|
242
|
+
# Use the default branch from FRAIM config
|
|
243
|
+
DEFAULT_BRANCH="$CONFIG_DEFAULT_BRANCH"
|
|
244
|
+
echo "✓ Using default branch from FRAIM config: $DEFAULT_BRANCH"
|
|
245
|
+
|
|
246
|
+
# Verify the branch exists on remote
|
|
247
|
+
if git ls-remote --exit-code --heads "$REPO_URL" "$DEFAULT_BRANCH" >/dev/null 2>&1; then
|
|
248
|
+
echo "✓ Confirmed branch '$DEFAULT_BRANCH' exists on remote"
|
|
249
|
+
else
|
|
250
|
+
echo "⚠️ Warning: Configured default branch '$DEFAULT_BRANCH' not found on remote"
|
|
251
|
+
echo " Falling back to branch detection..."
|
|
252
|
+
|
|
253
|
+
# Fallback to detection if configured branch doesn't exist
|
|
254
|
+
DEFAULT_BRANCH=""
|
|
255
|
+
for candidate in main master develop; do
|
|
256
|
+
if git ls-remote --exit-code --heads "$REPO_URL" "$candidate" >/dev/null 2>&1; then
|
|
257
|
+
DEFAULT_BRANCH="$candidate"
|
|
258
|
+
echo "✓ Detected fallback branch: $DEFAULT_BRANCH"
|
|
259
|
+
break
|
|
260
|
+
fi
|
|
261
|
+
done
|
|
262
|
+
|
|
263
|
+
# Final fallback to master
|
|
264
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
265
|
+
DEFAULT_BRANCH="master"
|
|
266
|
+
echo "⚠️ Could not detect any branch, defaulting to: $DEFAULT_BRANCH"
|
|
267
|
+
fi
|
|
237
268
|
fi
|
|
238
269
|
|
|
239
270
|
BASE_BRANCH="$DEFAULT_BRANCH"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Template: Marketing Storytelling
|
|
2
|
+
|
|
3
|
+
Use this template to transform product development experiences into compelling founder narratives for events and speaking opportunities.
|
|
4
|
+
|
|
5
|
+
## Project Setup
|
|
6
|
+
- **Product/Project Name**:
|
|
7
|
+
- **Issue Number**:
|
|
8
|
+
- **Target Event**:
|
|
9
|
+
- **Event Type**: [Conference Talk / Panel / Workshop / Podcast / Accelerator / Meetup]
|
|
10
|
+
- **Audience**: [Technical / Business / Mixed]
|
|
11
|
+
- **Event Date**:
|
|
12
|
+
- **Submission Deadline**:
|
|
13
|
+
|
|
14
|
+
## Story Arc Development
|
|
15
|
+
|
|
16
|
+
### Original Intent
|
|
17
|
+
**What was the vision?**
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
**Why did it matter?**
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
**What problem were you solving?**
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Friction Points
|
|
27
|
+
**What unexpected challenges emerged?**
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
**What failures or setbacks occurred?**
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
**What assumptions proved wrong?**
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Key Insights
|
|
37
|
+
**What "aha moments" happened?**
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
**What counterintuitive discoveries were made?**
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
**What would you do differently?**
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Final Outcome
|
|
47
|
+
**What was actually built?**
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
**Why does it work?**
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
**What makes it different/better?**
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## Founder Lessons (3-5 key takeaways)
|
|
57
|
+
|
|
58
|
+
### Lesson 1
|
|
59
|
+
**Lesson**:
|
|
60
|
+
**Context**:
|
|
61
|
+
**Application**:
|
|
62
|
+
|
|
63
|
+
### Lesson 2
|
|
64
|
+
**Lesson**:
|
|
65
|
+
**Context**:
|
|
66
|
+
**Application**:
|
|
67
|
+
|
|
68
|
+
### Lesson 3
|
|
69
|
+
**Lesson**:
|
|
70
|
+
**Context**:
|
|
71
|
+
**Application**:
|
|
72
|
+
|
|
73
|
+
## Event Content
|
|
74
|
+
|
|
75
|
+
### Session Title Options
|
|
76
|
+
1.
|
|
77
|
+
2.
|
|
78
|
+
3.
|
|
79
|
+
|
|
80
|
+
### Session Description (1-2 sentences)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Email Pitch (2-3 paragraphs)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### Speaker Bio (50-100 words)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
### Presentation Outline
|
|
90
|
+
|
|
91
|
+
#### 5-Minute Version
|
|
92
|
+
- **Hook** (1 min):
|
|
93
|
+
- **Problem** (1 min):
|
|
94
|
+
- **Journey** (2 min):
|
|
95
|
+
- **Takeaway** (1 min):
|
|
96
|
+
|
|
97
|
+
#### 15-Minute Version
|
|
98
|
+
- **Opening** (2 min):
|
|
99
|
+
- **Problem Setup** (3 min):
|
|
100
|
+
- **Journey/Challenges** (5 min):
|
|
101
|
+
- **Insights/Solutions** (3 min):
|
|
102
|
+
- **Takeaways** (2 min):
|
|
103
|
+
|
|
104
|
+
#### 30-Minute Version
|
|
105
|
+
- **Introduction** (3 min):
|
|
106
|
+
- **Context/Background** (5 min):
|
|
107
|
+
- **Challenge Deep-Dive** (8 min):
|
|
108
|
+
- **Solution Journey** (8 min):
|
|
109
|
+
- **Lessons & Applications** (4 min):
|
|
110
|
+
- **Q&A Prep** (2 min):
|
|
111
|
+
|
|
112
|
+
### Q&A Preparation
|
|
113
|
+
**Likely Questions**:
|
|
114
|
+
1. Q:
|
|
115
|
+
A:
|
|
116
|
+
|
|
117
|
+
2. Q:
|
|
118
|
+
A:
|
|
119
|
+
|
|
120
|
+
3. Q:
|
|
121
|
+
A:
|
|
122
|
+
|
|
123
|
+
## Quality Checklist
|
|
124
|
+
- [ ] Story is grounded in actual experience
|
|
125
|
+
- [ ] Avoids internal jargon
|
|
126
|
+
- [ ] Follows clear story arc (intent → friction → insight → outcome)
|
|
127
|
+
- [ ] Provides actionable lessons
|
|
128
|
+
- [ ] Sounds authentic, not sales-y
|
|
129
|
+
- [ ] Matches event format and audience
|
|
130
|
+
- [ ] Professional presentation materials ready
|