@ncukondo/slide-generation 0.7.0 → 0.8.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/dist/cli/index.js +647 -364
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +38 -34
- package/dist/index.js +276 -16
- package/dist/index.js.map +1 -1
- package/icons/fetched/LICENSE +202 -0
- package/icons/fetched/NOTICE +14 -0
- package/icons/fetched/heroicons/arrow-right.svg +1 -0
- package/icons/fetched/material-icons/account_balance.svg +1 -0
- package/icons/fetched/material-icons/analytics.svg +1 -0
- package/icons/fetched/material-icons/business.svg +1 -0
- package/icons/fetched/material-icons/check_circle.svg +1 -0
- package/icons/fetched/material-icons/computer.svg +1 -0
- package/icons/fetched/material-icons/description.svg +1 -0
- package/icons/fetched/material-icons/done_all.svg +1 -0
- package/icons/fetched/material-icons/event_note.svg +1 -0
- package/icons/fetched/material-icons/lightbulb.svg +1 -0
- package/icons/fetched/material-icons/person.svg +1 -0
- package/icons/fetched/material-icons/play_arrow.svg +1 -0
- package/icons/fetched/material-icons/replay.svg +1 -0
- package/icons/fetched/material-icons/thumb_up.svg +1 -0
- package/icons/fetched/material-icons/trending_up.svg +1 -0
- package/icons/registry.yaml +1 -0
- package/package.json +1 -1
- package/templates/diagrams/flow-chart.yaml +5 -5
- package/templates/diagrams/hierarchy.yaml +4 -1
- package/templates/layouts/before-after.yaml +15 -15
- package/templates/layouts/gallery.yaml +12 -12
- package/templates/layouts/image-caption.yaml +6 -6
- package/templates/layouts/image-full.yaml +9 -9
- package/templates/layouts/image-text.yaml +26 -24
- package/templates/layouts/three-column.yaml +5 -5
- package/templates/layouts/two-column.yaml +3 -3
- package/icons/fetched/.gitkeep +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -338,6 +338,13 @@ ${slidesContent}`;
|
|
|
338
338
|
lines.push(`${key}: ${this.formatFrontMatterValue(value)}`);
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
+
if (options?.templateCss && options.templateCss.trim()) {
|
|
342
|
+
lines.push("style: |");
|
|
343
|
+
const cssLines = options.templateCss.split("\n");
|
|
344
|
+
for (const line of cssLines) {
|
|
345
|
+
lines.push(` ${line}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
341
348
|
lines.push("---");
|
|
342
349
|
return lines.join("\n");
|
|
343
350
|
}
|
|
@@ -374,7 +381,7 @@ ${this.renderSpeakerNotes(note)}`;
|
|
|
374
381
|
}
|
|
375
382
|
parts.push(slideContent);
|
|
376
383
|
}
|
|
377
|
-
return parts.map((slide) => `---
|
|
384
|
+
return parts.map((slide, index) => index === 0 ? slide : `---
|
|
378
385
|
|
|
379
386
|
${slide}`).join("\n\n");
|
|
380
387
|
}
|
|
@@ -615,6 +622,46 @@ var init_loader = __esm({
|
|
|
615
622
|
}
|
|
616
623
|
});
|
|
617
624
|
|
|
625
|
+
// src/templates/css-collector.ts
|
|
626
|
+
var CSSCollector;
|
|
627
|
+
var init_css_collector = __esm({
|
|
628
|
+
"src/templates/css-collector.ts"() {
|
|
629
|
+
"use strict";
|
|
630
|
+
CSSCollector = class {
|
|
631
|
+
constructor(templateLoader) {
|
|
632
|
+
this.templateLoader = templateLoader;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Collect CSS from the specified templates
|
|
636
|
+
* @param templateNames - Names of templates to collect CSS from
|
|
637
|
+
* @returns Combined CSS string from all templates
|
|
638
|
+
*/
|
|
639
|
+
collect(templateNames) {
|
|
640
|
+
const uniqueNames = [...new Set(templateNames)];
|
|
641
|
+
const cssBlocks = [];
|
|
642
|
+
for (const name of uniqueNames) {
|
|
643
|
+
const template = this.templateLoader.get(name);
|
|
644
|
+
if (template?.css) {
|
|
645
|
+
cssBlocks.push(template.css);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return cssBlocks.join("\n\n");
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// src/templates/index.ts
|
|
655
|
+
var init_templates = __esm({
|
|
656
|
+
"src/templates/index.ts"() {
|
|
657
|
+
"use strict";
|
|
658
|
+
init_engine();
|
|
659
|
+
init_loader();
|
|
660
|
+
init_validators();
|
|
661
|
+
init_css_collector();
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
618
665
|
// src/icons/schema.ts
|
|
619
666
|
import { z as z4 } from "zod";
|
|
620
667
|
var iconSourceTypeSchema, iconSourceSchema, iconDefaultsSchema, iconRegistrySchema;
|
|
@@ -757,24 +804,217 @@ var init_registry = __esm({
|
|
|
757
804
|
}
|
|
758
805
|
});
|
|
759
806
|
|
|
760
|
-
// src/icons/
|
|
807
|
+
// src/icons/fetcher.ts
|
|
761
808
|
import * as fs3 from "fs/promises";
|
|
762
809
|
import * as path2 from "path";
|
|
810
|
+
import { stringify as stringifyYaml, parse as parseYaml3 } from "yaml";
|
|
811
|
+
function isExternalSource(prefix) {
|
|
812
|
+
return ICON_SOURCES[prefix]?.external ?? false;
|
|
813
|
+
}
|
|
814
|
+
function isValidIconName(name, allowPath = false) {
|
|
815
|
+
if (!name || name.length > 100) {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
if (name.includes("..") || name.startsWith("/") || name.endsWith("/")) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
const pattern = allowPath ? VALID_ICON_NAME_WITH_PATH : VALID_ICON_NAME;
|
|
822
|
+
return pattern.test(name);
|
|
823
|
+
}
|
|
824
|
+
var ICON_SOURCES, VALID_ICON_NAME, VALID_ICON_NAME_WITH_PATH, IconFetcher;
|
|
825
|
+
var init_fetcher = __esm({
|
|
826
|
+
"src/icons/fetcher.ts"() {
|
|
827
|
+
"use strict";
|
|
828
|
+
ICON_SOURCES = {
|
|
829
|
+
health: { set: "healthicons", license: "MIT", external: true },
|
|
830
|
+
ms: { set: "material-symbols", license: "Apache-2.0", external: true },
|
|
831
|
+
hero: { set: "heroicons", license: "MIT", external: true },
|
|
832
|
+
mi: { set: "material-icons", license: "Apache-2.0", external: false },
|
|
833
|
+
mdi: { set: "mdi", license: "Apache-2.0", external: true },
|
|
834
|
+
iconify: { set: "iconify", license: "Various", external: true }
|
|
835
|
+
};
|
|
836
|
+
VALID_ICON_NAME = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/i;
|
|
837
|
+
VALID_ICON_NAME_WITH_PATH = /^[a-z0-9][a-z0-9-/]*[a-z0-9]$|^[a-z0-9]$/i;
|
|
838
|
+
IconFetcher = class {
|
|
839
|
+
fetchedDir;
|
|
840
|
+
saveLocally;
|
|
841
|
+
timeoutMs;
|
|
842
|
+
constructor(options = {}) {
|
|
843
|
+
this.fetchedDir = options.fetchedDir ?? "icons/fetched";
|
|
844
|
+
this.saveLocally = options.saveLocally ?? true;
|
|
845
|
+
this.timeoutMs = options.timeoutMs ?? 1e4;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Parse an icon reference string (e.g., "health:stethoscope")
|
|
849
|
+
* Returns null if the format is invalid or contains unsafe characters
|
|
850
|
+
*/
|
|
851
|
+
parseReference(iconRef) {
|
|
852
|
+
const colonIndex = iconRef.indexOf(":");
|
|
853
|
+
if (colonIndex === -1) {
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
const prefix = iconRef.substring(0, colonIndex);
|
|
857
|
+
const name = iconRef.substring(colonIndex + 1);
|
|
858
|
+
if (!/^[a-z0-9]+$/i.test(prefix)) {
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
if (!isValidIconName(name)) {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
return { prefix, name };
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Get the Iconify set name for a prefix
|
|
868
|
+
*/
|
|
869
|
+
getIconifySet(prefix) {
|
|
870
|
+
return ICON_SOURCES[prefix]?.set ?? prefix;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Get the local file path for an icon reference
|
|
874
|
+
*/
|
|
875
|
+
getLocalPath(iconRef) {
|
|
876
|
+
const parsed = this.parseReference(iconRef);
|
|
877
|
+
if (!parsed) {
|
|
878
|
+
throw new Error(`Invalid icon reference: ${iconRef}`);
|
|
879
|
+
}
|
|
880
|
+
const setDir = this.getIconifySet(parsed.prefix);
|
|
881
|
+
return path2.join(this.fetchedDir, setDir, `${parsed.name}.svg`);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Check if an icon exists locally
|
|
885
|
+
*/
|
|
886
|
+
async existsLocally(iconRef) {
|
|
887
|
+
const localPath = this.getLocalPath(iconRef);
|
|
888
|
+
try {
|
|
889
|
+
await fs3.access(localPath);
|
|
890
|
+
return true;
|
|
891
|
+
} catch {
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Build the Iconify API URL for an icon
|
|
897
|
+
*/
|
|
898
|
+
buildUrl(prefix, name) {
|
|
899
|
+
const set = this.getIconifySet(prefix);
|
|
900
|
+
return `https://api.iconify.design/${set}/${name}.svg`;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Resolve an icon reference (local first, then fetch)
|
|
904
|
+
*/
|
|
905
|
+
async resolve(iconRef) {
|
|
906
|
+
if (await this.existsLocally(iconRef)) {
|
|
907
|
+
const localPath = this.getLocalPath(iconRef);
|
|
908
|
+
return await fs3.readFile(localPath, "utf-8");
|
|
909
|
+
}
|
|
910
|
+
return await this.fetchAndSave(iconRef);
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Fetch an icon from external source and save locally
|
|
914
|
+
*/
|
|
915
|
+
async fetchAndSave(iconRef) {
|
|
916
|
+
const parsed = this.parseReference(iconRef);
|
|
917
|
+
if (!parsed) {
|
|
918
|
+
throw new Error(`Invalid icon reference: ${iconRef}`);
|
|
919
|
+
}
|
|
920
|
+
const url = this.buildUrl(parsed.prefix, parsed.name);
|
|
921
|
+
const controller = new AbortController();
|
|
922
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
923
|
+
let response;
|
|
924
|
+
try {
|
|
925
|
+
response = await fetch(url, { signal: controller.signal });
|
|
926
|
+
} catch (error) {
|
|
927
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
928
|
+
throw new Error(`Fetch timeout: ${iconRef} (exceeded ${this.timeoutMs}ms)`);
|
|
929
|
+
}
|
|
930
|
+
throw error;
|
|
931
|
+
} finally {
|
|
932
|
+
clearTimeout(timeoutId);
|
|
933
|
+
}
|
|
934
|
+
if (!response.ok) {
|
|
935
|
+
throw new Error(`Icon not found: ${iconRef} (HTTP ${response.status})`);
|
|
936
|
+
}
|
|
937
|
+
const svg = await response.text();
|
|
938
|
+
if (this.saveLocally) {
|
|
939
|
+
await this.saveLocallyInternal(parsed.prefix, parsed.name, svg, url);
|
|
940
|
+
}
|
|
941
|
+
return svg;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Save icon to local filesystem
|
|
945
|
+
*/
|
|
946
|
+
async saveLocallyInternal(prefix, name, svg, sourceUrl) {
|
|
947
|
+
const setDir = this.getIconifySet(prefix);
|
|
948
|
+
const dirPath = path2.join(this.fetchedDir, setDir);
|
|
949
|
+
const filePath = path2.join(dirPath, `${name}.svg`);
|
|
950
|
+
await fs3.mkdir(dirPath, { recursive: true });
|
|
951
|
+
await fs3.writeFile(filePath, svg, "utf-8");
|
|
952
|
+
await this.updateSourcesYaml(setDir, name, sourceUrl);
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Update _sources.yaml with source information
|
|
956
|
+
*/
|
|
957
|
+
async updateSourcesYaml(setDir, name, sourceUrl) {
|
|
958
|
+
const sourcesPath = path2.join(this.fetchedDir, "_sources.yaml");
|
|
959
|
+
let sources = {};
|
|
960
|
+
try {
|
|
961
|
+
const content = await fs3.readFile(sourcesPath, "utf-8");
|
|
962
|
+
sources = parseYaml3(content) ?? {};
|
|
963
|
+
} catch {
|
|
964
|
+
}
|
|
965
|
+
const key = `${setDir}/${name}.svg`;
|
|
966
|
+
sources[key] = {
|
|
967
|
+
source: sourceUrl,
|
|
968
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
969
|
+
license: this.getLicense(setDir)
|
|
970
|
+
};
|
|
971
|
+
const header = `# Fetched icon sources
|
|
972
|
+
# This file tracks where icons were fetched from for traceability
|
|
973
|
+
|
|
974
|
+
`;
|
|
975
|
+
await fs3.writeFile(sourcesPath, header + stringifyYaml(sources), "utf-8");
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Get the license for an icon set
|
|
979
|
+
*/
|
|
980
|
+
getLicense(setDir) {
|
|
981
|
+
for (const config of Object.values(ICON_SOURCES)) {
|
|
982
|
+
if (config.set === setDir) {
|
|
983
|
+
return config.license;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return "Unknown";
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
// src/icons/resolver.ts
|
|
993
|
+
import * as fs4 from "fs/promises";
|
|
994
|
+
import * as path3 from "path";
|
|
763
995
|
import nunjucks2 from "nunjucks";
|
|
764
996
|
var IconResolver;
|
|
765
997
|
var init_resolver = __esm({
|
|
766
998
|
"src/icons/resolver.ts"() {
|
|
767
999
|
"use strict";
|
|
1000
|
+
init_fetcher();
|
|
768
1001
|
IconResolver = class {
|
|
769
1002
|
constructor(registry, options = {}) {
|
|
770
1003
|
this.registry = registry;
|
|
771
1004
|
this.nunjucksEnv = new nunjucks2.Environment(null, {
|
|
772
1005
|
autoescape: false
|
|
773
1006
|
});
|
|
774
|
-
this.options =
|
|
1007
|
+
this.options = {
|
|
1008
|
+
autoFetch: true,
|
|
1009
|
+
...options
|
|
1010
|
+
};
|
|
1011
|
+
this.fetcher = new IconFetcher({
|
|
1012
|
+
fetchedDir: options.fetchedDir ?? "icons/fetched"
|
|
1013
|
+
});
|
|
775
1014
|
}
|
|
776
1015
|
nunjucksEnv;
|
|
777
1016
|
options;
|
|
1017
|
+
fetcher;
|
|
778
1018
|
/**
|
|
779
1019
|
* Render an icon by name or alias
|
|
780
1020
|
*/
|
|
@@ -796,6 +1036,17 @@ var init_resolver = __esm({
|
|
|
796
1036
|
color: this.resolveColor(options?.color) ?? defaults.color,
|
|
797
1037
|
...options?.class !== void 0 ? { class: options.class } : {}
|
|
798
1038
|
};
|
|
1039
|
+
const cachedSvg = await this.tryGetCachedSvg(parsed.prefix, parsed.name);
|
|
1040
|
+
if (cachedSvg) {
|
|
1041
|
+
return this.processSvg(cachedSvg, parsed.name, mergedOptions);
|
|
1042
|
+
}
|
|
1043
|
+
if (this.options.autoFetch && this.isFetchableSource(parsed.prefix)) {
|
|
1044
|
+
try {
|
|
1045
|
+
const fetchedSvg = await this.fetcher.fetchAndSave(`${parsed.prefix}:${parsed.name}`);
|
|
1046
|
+
return this.processSvg(fetchedSvg, parsed.name, mergedOptions);
|
|
1047
|
+
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
799
1050
|
switch (source.type) {
|
|
800
1051
|
case "web-font":
|
|
801
1052
|
return this.renderWebFont(source, parsed.name, mergedOptions);
|
|
@@ -809,6 +1060,29 @@ var init_resolver = __esm({
|
|
|
809
1060
|
throw new Error(`Unsupported icon source type: "${source.type}"`);
|
|
810
1061
|
}
|
|
811
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Try to get a cached SVG from the fetched directory
|
|
1065
|
+
*/
|
|
1066
|
+
async tryGetCachedSvg(prefix, name) {
|
|
1067
|
+
const iconSourceConfig = ICON_SOURCES[prefix];
|
|
1068
|
+
if (!iconSourceConfig) {
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
const setDir = iconSourceConfig.set;
|
|
1072
|
+
const fetchedDir = this.options.fetchedDir ?? "icons/fetched";
|
|
1073
|
+
const svgPath = path3.join(fetchedDir, setDir, `${name}.svg`);
|
|
1074
|
+
try {
|
|
1075
|
+
return await fs4.readFile(svgPath, "utf-8");
|
|
1076
|
+
} catch {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Check if a source prefix is fetchable from external API
|
|
1082
|
+
*/
|
|
1083
|
+
isFetchableSource(prefix) {
|
|
1084
|
+
return prefix in ICON_SOURCES;
|
|
1085
|
+
}
|
|
812
1086
|
/**
|
|
813
1087
|
* Render a web-font icon
|
|
814
1088
|
*/
|
|
@@ -833,9 +1107,9 @@ var init_resolver = __esm({
|
|
|
833
1107
|
if (!source.path) {
|
|
834
1108
|
throw new Error(`Local SVG source "${source.name}" has no path defined`);
|
|
835
1109
|
}
|
|
836
|
-
const svgPath =
|
|
1110
|
+
const svgPath = path3.join(source.path, `${name}.svg`);
|
|
837
1111
|
try {
|
|
838
|
-
const svgContent = await
|
|
1112
|
+
const svgContent = await fs4.readFile(svgPath, "utf-8");
|
|
839
1113
|
return this.processSvg(svgContent, name, options);
|
|
840
1114
|
} catch (error) {
|
|
841
1115
|
if (error.code === "ENOENT") {
|
|
@@ -982,6 +1256,8 @@ var init_manager = __esm({
|
|
|
982
1256
|
}
|
|
983
1257
|
/**
|
|
984
1258
|
* Get multiple references by IDs using ref export for better performance
|
|
1259
|
+
* Note: ref export returns exit code 1 when references are not found,
|
|
1260
|
+
* but still outputs valid JSON (empty array or partial results) to stdout
|
|
985
1261
|
*/
|
|
986
1262
|
async getByIds(ids) {
|
|
987
1263
|
if (ids.length === 0) {
|
|
@@ -989,7 +1265,8 @@ var init_manager = __esm({
|
|
|
989
1265
|
}
|
|
990
1266
|
const idsArg = ids.map((id) => `"${id}"`).join(" ");
|
|
991
1267
|
const result = await this.execCommand(
|
|
992
|
-
`${this.command} export ${idsArg}
|
|
1268
|
+
`${this.command} export ${idsArg}`,
|
|
1269
|
+
true
|
|
993
1270
|
);
|
|
994
1271
|
const items = this.parseJSON(result);
|
|
995
1272
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -998,16 +1275,16 @@ var init_manager = __esm({
|
|
|
998
1275
|
}
|
|
999
1276
|
return map;
|
|
1000
1277
|
}
|
|
1001
|
-
execCommand(cmd) {
|
|
1002
|
-
return new Promise((
|
|
1278
|
+
execCommand(cmd, ignoreExitCode = false) {
|
|
1279
|
+
return new Promise((resolve9, reject) => {
|
|
1003
1280
|
exec(cmd, (error, stdout) => {
|
|
1004
|
-
if (error) {
|
|
1281
|
+
if (error && !ignoreExitCode) {
|
|
1005
1282
|
reject(
|
|
1006
1283
|
new ReferenceManagerError(`Failed to execute: ${cmd}`, error)
|
|
1007
1284
|
);
|
|
1008
1285
|
return;
|
|
1009
1286
|
}
|
|
1010
|
-
|
|
1287
|
+
resolve9(stdout.toString());
|
|
1011
1288
|
});
|
|
1012
1289
|
});
|
|
1013
1290
|
}
|
|
@@ -1462,7 +1739,7 @@ var init_bibliography = __esm({
|
|
|
1462
1739
|
});
|
|
1463
1740
|
|
|
1464
1741
|
// src/core/pipeline.ts
|
|
1465
|
-
import { writeFile } from "fs/promises";
|
|
1742
|
+
import { writeFile as writeFile2 } from "fs/promises";
|
|
1466
1743
|
var PipelineError, Pipeline;
|
|
1467
1744
|
var init_pipeline = __esm({
|
|
1468
1745
|
"src/core/pipeline.ts"() {
|
|
@@ -1471,7 +1748,7 @@ var init_pipeline = __esm({
|
|
|
1471
1748
|
init_transformer();
|
|
1472
1749
|
init_renderer();
|
|
1473
1750
|
init_engine();
|
|
1474
|
-
|
|
1751
|
+
init_templates();
|
|
1475
1752
|
init_registry();
|
|
1476
1753
|
init_resolver();
|
|
1477
1754
|
init_manager();
|
|
@@ -1493,7 +1770,9 @@ var init_pipeline = __esm({
|
|
|
1493
1770
|
this.templateEngine = new TemplateEngine();
|
|
1494
1771
|
this.templateLoader = new TemplateLoader();
|
|
1495
1772
|
this.iconRegistry = new IconRegistryLoader();
|
|
1496
|
-
this.iconResolver = new IconResolver(this.iconRegistry
|
|
1773
|
+
this.iconResolver = new IconResolver(this.iconRegistry, {
|
|
1774
|
+
fetchedDir: config.icons.fetched
|
|
1775
|
+
});
|
|
1497
1776
|
this.referenceManager = new ReferenceManager(
|
|
1498
1777
|
config.references.connection.command
|
|
1499
1778
|
);
|
|
@@ -1520,6 +1799,7 @@ var init_pipeline = __esm({
|
|
|
1520
1799
|
this.citationFormatter
|
|
1521
1800
|
);
|
|
1522
1801
|
this.renderer = new Renderer();
|
|
1802
|
+
this.cssCollector = new CSSCollector(this.templateLoader);
|
|
1523
1803
|
}
|
|
1524
1804
|
parser;
|
|
1525
1805
|
templateEngine;
|
|
@@ -1532,6 +1812,7 @@ var init_pipeline = __esm({
|
|
|
1532
1812
|
bibliographyGenerator;
|
|
1533
1813
|
transformer;
|
|
1534
1814
|
renderer;
|
|
1815
|
+
cssCollector;
|
|
1535
1816
|
warnings = [];
|
|
1536
1817
|
/**
|
|
1537
1818
|
* Run the full conversion pipeline
|
|
@@ -1546,7 +1827,7 @@ var init_pipeline = __esm({
|
|
|
1546
1827
|
const transformedSlides = await this.transformSlides(presentation);
|
|
1547
1828
|
const output = this.render(transformedSlides, presentation);
|
|
1548
1829
|
if (options?.outputPath) {
|
|
1549
|
-
await
|
|
1830
|
+
await writeFile2(options.outputPath, output, "utf-8");
|
|
1550
1831
|
}
|
|
1551
1832
|
return output;
|
|
1552
1833
|
} catch (error) {
|
|
@@ -1573,7 +1854,7 @@ var init_pipeline = __esm({
|
|
|
1573
1854
|
const transformedSlides = await this.transformSlides(presentation);
|
|
1574
1855
|
const output = this.render(transformedSlides, presentation);
|
|
1575
1856
|
if (options?.outputPath) {
|
|
1576
|
-
await
|
|
1857
|
+
await writeFile2(options.outputPath, output, "utf-8");
|
|
1577
1858
|
}
|
|
1578
1859
|
return {
|
|
1579
1860
|
output,
|
|
@@ -1687,9 +1968,12 @@ var init_pipeline = __esm({
|
|
|
1687
1968
|
render(slides, presentation) {
|
|
1688
1969
|
try {
|
|
1689
1970
|
const notes = presentation.slides.map((slide) => slide.notes);
|
|
1971
|
+
const templateNames = presentation.slides.map((slide) => slide.template);
|
|
1972
|
+
const templateCss = this.cssCollector.collect(templateNames);
|
|
1690
1973
|
const renderOptions = {
|
|
1691
1974
|
includeTheme: true,
|
|
1692
|
-
notes
|
|
1975
|
+
notes,
|
|
1976
|
+
...templateCss ? { templateCss } : {}
|
|
1693
1977
|
};
|
|
1694
1978
|
return this.renderer.render(slides, presentation.meta, renderOptions);
|
|
1695
1979
|
} catch (error) {
|
|
@@ -1804,11 +2088,7 @@ var init_schema2 = __esm({
|
|
|
1804
2088
|
}).default({}),
|
|
1805
2089
|
icons: z5.object({
|
|
1806
2090
|
registry: z5.string().default("./icons/registry.yaml"),
|
|
1807
|
-
|
|
1808
|
-
enabled: z5.boolean().default(true),
|
|
1809
|
-
directory: z5.string().default(".cache/icons"),
|
|
1810
|
-
ttl: z5.number().default(86400)
|
|
1811
|
-
}).default({})
|
|
2091
|
+
fetched: z5.string().default("./icons/fetched")
|
|
1812
2092
|
}).default({}),
|
|
1813
2093
|
references: z5.object({
|
|
1814
2094
|
enabled: z5.boolean().default(true),
|
|
@@ -1834,9 +2114,9 @@ var init_schema2 = __esm({
|
|
|
1834
2114
|
});
|
|
1835
2115
|
|
|
1836
2116
|
// src/config/loader.ts
|
|
1837
|
-
import { access, readFile as
|
|
1838
|
-
import { join as
|
|
1839
|
-
import { parse as
|
|
2117
|
+
import { access as access2, readFile as readFile6 } from "fs/promises";
|
|
2118
|
+
import { join as join4 } from "path";
|
|
2119
|
+
import { parse as parseYaml4 } from "yaml";
|
|
1840
2120
|
var CONFIG_NAMES, ConfigLoader;
|
|
1841
2121
|
var init_loader2 = __esm({
|
|
1842
2122
|
"src/config/loader.ts"() {
|
|
@@ -1850,9 +2130,9 @@ var init_loader2 = __esm({
|
|
|
1850
2130
|
}
|
|
1851
2131
|
async findConfig(directory) {
|
|
1852
2132
|
for (const name of CONFIG_NAMES) {
|
|
1853
|
-
const path12 =
|
|
2133
|
+
const path12 = join4(directory, name);
|
|
1854
2134
|
try {
|
|
1855
|
-
await
|
|
2135
|
+
await access2(path12);
|
|
1856
2136
|
return path12;
|
|
1857
2137
|
} catch {
|
|
1858
2138
|
}
|
|
@@ -1862,8 +2142,8 @@ var init_loader2 = __esm({
|
|
|
1862
2142
|
async loadFile(configPath) {
|
|
1863
2143
|
if (!configPath) return {};
|
|
1864
2144
|
try {
|
|
1865
|
-
const content = await
|
|
1866
|
-
return
|
|
2145
|
+
const content = await readFile6(configPath, "utf-8");
|
|
2146
|
+
return parseYaml4(content) ?? {};
|
|
1867
2147
|
} catch (error) {
|
|
1868
2148
|
if (error.code === "ENOENT") {
|
|
1869
2149
|
return {};
|
|
@@ -1995,9 +2275,9 @@ var init_schema3 = __esm({
|
|
|
1995
2275
|
});
|
|
1996
2276
|
|
|
1997
2277
|
// src/images/metadata-loader.ts
|
|
1998
|
-
import * as
|
|
1999
|
-
import * as
|
|
2000
|
-
import { parse as
|
|
2278
|
+
import * as fs5 from "fs/promises";
|
|
2279
|
+
import * as path4 from "path";
|
|
2280
|
+
import { parse as parseYaml5 } from "yaml";
|
|
2001
2281
|
var metadataWarnings, ImageMetadataLoader;
|
|
2002
2282
|
var init_metadata_loader = __esm({
|
|
2003
2283
|
"src/images/metadata-loader.ts"() {
|
|
@@ -2026,16 +2306,16 @@ var init_metadata_loader = __esm({
|
|
|
2026
2306
|
*/
|
|
2027
2307
|
async loadDirectory(dirPath) {
|
|
2028
2308
|
const result = /* @__PURE__ */ new Map();
|
|
2029
|
-
const fullDirPath =
|
|
2309
|
+
const fullDirPath = path4.join(this.baseDir, dirPath);
|
|
2030
2310
|
try {
|
|
2031
|
-
await
|
|
2311
|
+
await fs5.access(fullDirPath);
|
|
2032
2312
|
} catch {
|
|
2033
2313
|
return result;
|
|
2034
2314
|
}
|
|
2035
|
-
const files = await
|
|
2315
|
+
const files = await fs5.readdir(fullDirPath);
|
|
2036
2316
|
const imageFiles = files.filter((f) => isImageFile(f));
|
|
2037
2317
|
for (const file of imageFiles) {
|
|
2038
|
-
const imagePath =
|
|
2318
|
+
const imagePath = path4.join(dirPath, file);
|
|
2039
2319
|
const metadata = await this.load(imagePath);
|
|
2040
2320
|
result.set(file, metadata);
|
|
2041
2321
|
}
|
|
@@ -2047,7 +2327,7 @@ var init_metadata_loader = __esm({
|
|
|
2047
2327
|
async hasMetadata(imagePath) {
|
|
2048
2328
|
const individualPath = this.getIndividualMetadataPath(imagePath);
|
|
2049
2329
|
try {
|
|
2050
|
-
await
|
|
2330
|
+
await fs5.access(individualPath);
|
|
2051
2331
|
return true;
|
|
2052
2332
|
} catch {
|
|
2053
2333
|
}
|
|
@@ -2055,7 +2335,7 @@ var init_metadata_loader = __esm({
|
|
|
2055
2335
|
if (dirMetadata === null) {
|
|
2056
2336
|
return false;
|
|
2057
2337
|
}
|
|
2058
|
-
const filename =
|
|
2338
|
+
const filename = path4.basename(imagePath);
|
|
2059
2339
|
return filename in dirMetadata && filename !== "_defaults";
|
|
2060
2340
|
}
|
|
2061
2341
|
/**
|
|
@@ -2069,14 +2349,14 @@ var init_metadata_loader = __esm({
|
|
|
2069
2349
|
* Get the path to individual metadata file
|
|
2070
2350
|
*/
|
|
2071
2351
|
getIndividualMetadataPath(imagePath) {
|
|
2072
|
-
return
|
|
2352
|
+
return path4.join(this.baseDir, `${imagePath}.meta.yaml`);
|
|
2073
2353
|
}
|
|
2074
2354
|
/**
|
|
2075
2355
|
* Get the path to directory metadata file
|
|
2076
2356
|
*/
|
|
2077
2357
|
getDirectoryMetadataPath(imagePath) {
|
|
2078
|
-
const dir =
|
|
2079
|
-
return
|
|
2358
|
+
const dir = path4.dirname(imagePath);
|
|
2359
|
+
return path4.join(this.baseDir, dir, "images.yaml");
|
|
2080
2360
|
}
|
|
2081
2361
|
/**
|
|
2082
2362
|
* Load individual metadata file (.meta.yaml)
|
|
@@ -2084,13 +2364,13 @@ var init_metadata_loader = __esm({
|
|
|
2084
2364
|
async loadIndividualMetadata(imagePath) {
|
|
2085
2365
|
const metadataPath = this.getIndividualMetadataPath(imagePath);
|
|
2086
2366
|
try {
|
|
2087
|
-
await
|
|
2367
|
+
await fs5.access(metadataPath);
|
|
2088
2368
|
} catch {
|
|
2089
2369
|
return null;
|
|
2090
2370
|
}
|
|
2091
2371
|
try {
|
|
2092
|
-
const content = await
|
|
2093
|
-
const parsed =
|
|
2372
|
+
const content = await fs5.readFile(metadataPath, "utf-8");
|
|
2373
|
+
const parsed = parseYaml5(content);
|
|
2094
2374
|
const validated = individualMetadataSchema.safeParse(parsed);
|
|
2095
2375
|
if (validated.success) {
|
|
2096
2376
|
return validated.data;
|
|
@@ -2115,13 +2395,13 @@ var init_metadata_loader = __esm({
|
|
|
2115
2395
|
async loadDirectoryMetadataFile(imagePath) {
|
|
2116
2396
|
const metadataPath = this.getDirectoryMetadataPath(imagePath);
|
|
2117
2397
|
try {
|
|
2118
|
-
await
|
|
2398
|
+
await fs5.access(metadataPath);
|
|
2119
2399
|
} catch {
|
|
2120
2400
|
return null;
|
|
2121
2401
|
}
|
|
2122
2402
|
try {
|
|
2123
|
-
const content = await
|
|
2124
|
-
const parsed =
|
|
2403
|
+
const content = await fs5.readFile(metadataPath, "utf-8");
|
|
2404
|
+
const parsed = parseYaml5(content);
|
|
2125
2405
|
const validated = directoryMetadataSchema.safeParse(parsed);
|
|
2126
2406
|
if (validated.success) {
|
|
2127
2407
|
return validated.data;
|
|
@@ -2148,7 +2428,7 @@ var init_metadata_loader = __esm({
|
|
|
2148
2428
|
if (dirMetadata === null) {
|
|
2149
2429
|
return {};
|
|
2150
2430
|
}
|
|
2151
|
-
const filename =
|
|
2431
|
+
const filename = path4.basename(imagePath);
|
|
2152
2432
|
const defaults = dirMetadata["_defaults"] ?? {};
|
|
2153
2433
|
const fileMetadata = dirMetadata[filename] ?? {};
|
|
2154
2434
|
return this.mergeMetadata(defaults, fileMetadata);
|
|
@@ -2183,8 +2463,8 @@ var init_metadata_loader = __esm({
|
|
|
2183
2463
|
});
|
|
2184
2464
|
|
|
2185
2465
|
// src/images/validator.ts
|
|
2186
|
-
import * as
|
|
2187
|
-
import * as
|
|
2466
|
+
import * as fs6 from "fs/promises";
|
|
2467
|
+
import * as path5 from "path";
|
|
2188
2468
|
import imageSize from "image-size";
|
|
2189
2469
|
var ImageValidator;
|
|
2190
2470
|
var init_validator = __esm({
|
|
@@ -2204,14 +2484,14 @@ var init_validator = __esm({
|
|
|
2204
2484
|
async validateImageExists(imagePath) {
|
|
2205
2485
|
const errors = [];
|
|
2206
2486
|
const warnings = [];
|
|
2207
|
-
const fullPath =
|
|
2208
|
-
const ext =
|
|
2487
|
+
const fullPath = path5.join(this.baseDir, imagePath);
|
|
2488
|
+
const ext = path5.extname(imagePath).toLowerCase();
|
|
2209
2489
|
if (!IMAGE_EXTENSIONS.has(ext)) {
|
|
2210
2490
|
errors.push(`Unsupported image format: ${imagePath}`);
|
|
2211
2491
|
return { valid: false, errors, warnings };
|
|
2212
2492
|
}
|
|
2213
2493
|
try {
|
|
2214
|
-
await
|
|
2494
|
+
await fs6.access(fullPath);
|
|
2215
2495
|
} catch {
|
|
2216
2496
|
errors.push(`Image not found: ${imagePath}`);
|
|
2217
2497
|
return { valid: false, errors, warnings };
|
|
@@ -2222,9 +2502,9 @@ var init_validator = __esm({
|
|
|
2222
2502
|
* Get image dimensions
|
|
2223
2503
|
*/
|
|
2224
2504
|
async getImageDimensions(imagePath) {
|
|
2225
|
-
const fullPath =
|
|
2505
|
+
const fullPath = path5.join(this.baseDir, imagePath);
|
|
2226
2506
|
try {
|
|
2227
|
-
const buffer = await
|
|
2507
|
+
const buffer = await fs6.readFile(fullPath);
|
|
2228
2508
|
const dimensions = imageSize(buffer);
|
|
2229
2509
|
if (dimensions.width && dimensions.height) {
|
|
2230
2510
|
return {
|
|
@@ -2559,8 +2839,8 @@ var init_processor = __esm({
|
|
|
2559
2839
|
});
|
|
2560
2840
|
|
|
2561
2841
|
// src/images/processing-pipeline.ts
|
|
2562
|
-
import * as
|
|
2563
|
-
import * as
|
|
2842
|
+
import * as fs7 from "fs/promises";
|
|
2843
|
+
import * as path6 from "path";
|
|
2564
2844
|
var ImageProcessingPipeline;
|
|
2565
2845
|
var init_processing_pipeline = __esm({
|
|
2566
2846
|
"src/images/processing-pipeline.ts"() {
|
|
@@ -2573,7 +2853,7 @@ var init_processing_pipeline = __esm({
|
|
|
2573
2853
|
this.baseDir = baseDir;
|
|
2574
2854
|
this.metadataLoader = new ImageMetadataLoader(baseDir);
|
|
2575
2855
|
this.processor = new ImageProcessor();
|
|
2576
|
-
this.outputDir = options?.outputDir ??
|
|
2856
|
+
this.outputDir = options?.outputDir ?? path6.join(baseDir, ".processed");
|
|
2577
2857
|
}
|
|
2578
2858
|
metadataLoader;
|
|
2579
2859
|
processor;
|
|
@@ -2582,7 +2862,7 @@ var init_processing_pipeline = __esm({
|
|
|
2582
2862
|
* Process a single image based on its metadata instructions
|
|
2583
2863
|
*/
|
|
2584
2864
|
async processImage(imagePath) {
|
|
2585
|
-
const fullPath =
|
|
2865
|
+
const fullPath = path6.join(this.baseDir, imagePath);
|
|
2586
2866
|
const result = {
|
|
2587
2867
|
success: true,
|
|
2588
2868
|
originalPath: fullPath
|
|
@@ -2593,7 +2873,7 @@ var init_processing_pipeline = __esm({
|
|
|
2593
2873
|
result.skipped = true;
|
|
2594
2874
|
return result;
|
|
2595
2875
|
}
|
|
2596
|
-
await
|
|
2876
|
+
await fs7.mkdir(this.outputDir, { recursive: true });
|
|
2597
2877
|
const processedPath = await this.applyInstructions(
|
|
2598
2878
|
fullPath,
|
|
2599
2879
|
imagePath,
|
|
@@ -2622,7 +2902,7 @@ var init_processing_pipeline = __esm({
|
|
|
2622
2902
|
imageMap: /* @__PURE__ */ new Map()
|
|
2623
2903
|
};
|
|
2624
2904
|
try {
|
|
2625
|
-
const files = await
|
|
2905
|
+
const files = await fs7.readdir(this.baseDir);
|
|
2626
2906
|
const imageFiles = files.filter((f) => isImageFile(f));
|
|
2627
2907
|
result.totalImages = imageFiles.length;
|
|
2628
2908
|
for (const imageFile of imageFiles) {
|
|
@@ -2646,11 +2926,11 @@ var init_processing_pipeline = __esm({
|
|
|
2646
2926
|
* Apply processing instructions to an image
|
|
2647
2927
|
*/
|
|
2648
2928
|
async applyInstructions(inputPath, relativePath, instructions) {
|
|
2649
|
-
const outputFilename =
|
|
2650
|
-
const finalOutputPath =
|
|
2929
|
+
const outputFilename = path6.basename(relativePath);
|
|
2930
|
+
const finalOutputPath = path6.join(this.outputDir, outputFilename);
|
|
2651
2931
|
const tempPaths = [
|
|
2652
|
-
|
|
2653
|
-
|
|
2932
|
+
path6.join(this.outputDir, `temp_${outputFilename}`),
|
|
2933
|
+
path6.join(this.outputDir, `temp2_${outputFilename}`)
|
|
2654
2934
|
];
|
|
2655
2935
|
let currentInputPath = inputPath;
|
|
2656
2936
|
for (let i = 0; i < instructions.length; i++) {
|
|
@@ -2663,11 +2943,11 @@ var init_processing_pipeline = __esm({
|
|
|
2663
2943
|
}
|
|
2664
2944
|
}
|
|
2665
2945
|
try {
|
|
2666
|
-
await
|
|
2946
|
+
await fs7.unlink(path6.join(this.outputDir, `temp_${outputFilename}`));
|
|
2667
2947
|
} catch {
|
|
2668
2948
|
}
|
|
2669
2949
|
try {
|
|
2670
|
-
await
|
|
2950
|
+
await fs7.unlink(path6.join(this.outputDir, `temp2_${outputFilename}`));
|
|
2671
2951
|
} catch {
|
|
2672
2952
|
}
|
|
2673
2953
|
return finalOutputPath;
|
|
@@ -2753,19 +3033,19 @@ var init_images = __esm({
|
|
|
2753
3033
|
|
|
2754
3034
|
// src/cli/commands/convert.ts
|
|
2755
3035
|
import { Command } from "commander";
|
|
2756
|
-
import { access as
|
|
2757
|
-
import { basename as basename3, dirname as dirname2, join as
|
|
3036
|
+
import { access as access5, readFile as readFile9 } from "fs/promises";
|
|
3037
|
+
import { basename as basename3, dirname as dirname2, join as join8, resolve, isAbsolute } from "path";
|
|
2758
3038
|
import chalk from "chalk";
|
|
2759
3039
|
import ora from "ora";
|
|
2760
|
-
import { parse as
|
|
3040
|
+
import { parse as parseYaml6 } from "yaml";
|
|
2761
3041
|
function getDefaultOutputPath(inputPath) {
|
|
2762
3042
|
const dir = dirname2(inputPath);
|
|
2763
3043
|
const base = basename3(inputPath, ".yaml");
|
|
2764
|
-
return
|
|
3044
|
+
return join8(dir, `${base}.md`);
|
|
2765
3045
|
}
|
|
2766
3046
|
async function extractImageDirectories(inputPath, baseDir) {
|
|
2767
|
-
const content = await
|
|
2768
|
-
const parsed =
|
|
3047
|
+
const content = await readFile9(inputPath, "utf-8");
|
|
3048
|
+
const parsed = parseYaml6(content);
|
|
2769
3049
|
const imageDirs = /* @__PURE__ */ new Set();
|
|
2770
3050
|
if (!parsed.slides) return [];
|
|
2771
3051
|
for (const slide of parsed.slides) {
|
|
@@ -2788,7 +3068,7 @@ async function extractImageDirectories(inputPath, baseDir) {
|
|
|
2788
3068
|
for (const imagePath of imagePaths) {
|
|
2789
3069
|
const dir = dirname2(imagePath);
|
|
2790
3070
|
if (dir && dir !== ".") {
|
|
2791
|
-
imageDirs.add(
|
|
3071
|
+
imageDirs.add(join8(baseDir, dir));
|
|
2792
3072
|
}
|
|
2793
3073
|
}
|
|
2794
3074
|
}
|
|
@@ -2799,7 +3079,7 @@ async function processImages(inputPath, baseDir) {
|
|
|
2799
3079
|
let totalProcessed = 0;
|
|
2800
3080
|
for (const imageDir of imageDirs) {
|
|
2801
3081
|
try {
|
|
2802
|
-
await
|
|
3082
|
+
await access5(imageDir);
|
|
2803
3083
|
const pipeline = new ImageProcessingPipeline(imageDir);
|
|
2804
3084
|
const result = await pipeline.processDirectory();
|
|
2805
3085
|
totalProcessed += result.processedImages;
|
|
@@ -2824,7 +3104,7 @@ async function executeConvert(inputPath, options) {
|
|
|
2824
3104
|
try {
|
|
2825
3105
|
spinner?.start(`Reading ${inputPath}...`);
|
|
2826
3106
|
try {
|
|
2827
|
-
await
|
|
3107
|
+
await access5(inputPath);
|
|
2828
3108
|
} catch {
|
|
2829
3109
|
spinner?.fail(`File not found: ${inputPath}`);
|
|
2830
3110
|
console.error(chalk.red(`Error: Input file not found: ${inputPath}`));
|
|
@@ -2839,6 +3119,19 @@ async function executeConvert(inputPath, options) {
|
|
|
2839
3119
|
configPath = await configLoader.findConfig(dirname2(inputPath));
|
|
2840
3120
|
}
|
|
2841
3121
|
const config = await configLoader.load(configPath);
|
|
3122
|
+
const configDir = configPath ? dirname2(configPath) : dirname2(inputPath);
|
|
3123
|
+
if (!isAbsolute(config.icons.fetched)) {
|
|
3124
|
+
config.icons.fetched = resolve(configDir, config.icons.fetched);
|
|
3125
|
+
}
|
|
3126
|
+
if (!isAbsolute(config.icons.registry)) {
|
|
3127
|
+
config.icons.registry = resolve(configDir, config.icons.registry);
|
|
3128
|
+
}
|
|
3129
|
+
if (!isAbsolute(config.templates.builtin)) {
|
|
3130
|
+
config.templates.builtin = resolve(configDir, config.templates.builtin);
|
|
3131
|
+
}
|
|
3132
|
+
if (config.templates.custom && !isAbsolute(config.templates.custom)) {
|
|
3133
|
+
config.templates.custom = resolve(configDir, config.templates.custom);
|
|
3134
|
+
}
|
|
2842
3135
|
if (options.references === false) {
|
|
2843
3136
|
config.references.enabled = false;
|
|
2844
3137
|
}
|
|
@@ -2942,16 +3235,16 @@ var init_convert = __esm({
|
|
|
2942
3235
|
|
|
2943
3236
|
// src/cli/utils/marp-runner.ts
|
|
2944
3237
|
import { existsSync } from "fs";
|
|
2945
|
-
import { join as
|
|
3238
|
+
import { join as join9, resolve as resolve2, dirname as dirname4 } from "path";
|
|
2946
3239
|
import {
|
|
2947
3240
|
execFileSync,
|
|
2948
3241
|
spawn
|
|
2949
3242
|
} from "child_process";
|
|
2950
3243
|
function getMarpCommand(projectDir) {
|
|
2951
|
-
const startDir =
|
|
3244
|
+
const startDir = resolve2(projectDir ?? process.cwd());
|
|
2952
3245
|
let currentDir = startDir;
|
|
2953
3246
|
while (true) {
|
|
2954
|
-
const localMarp =
|
|
3247
|
+
const localMarp = join9(currentDir, "node_modules", ".bin", "marp");
|
|
2955
3248
|
if (existsSync(localMarp)) {
|
|
2956
3249
|
return localMarp;
|
|
2957
3250
|
}
|
|
@@ -3013,13 +3306,20 @@ function parseMarpBrowserError(errorOutput) {
|
|
|
3013
3306
|
}
|
|
3014
3307
|
return null;
|
|
3015
3308
|
}
|
|
3309
|
+
function addHtmlOption(args) {
|
|
3310
|
+
if (args.includes("--html") || args.includes("--no-html")) {
|
|
3311
|
+
return args;
|
|
3312
|
+
}
|
|
3313
|
+
return ["--html", ...args];
|
|
3314
|
+
}
|
|
3016
3315
|
function runMarp(args, options = {}) {
|
|
3017
3316
|
const { projectDir, ...execOptions } = options;
|
|
3018
3317
|
const marpCmd = getMarpCommand(projectDir);
|
|
3019
3318
|
if (!marpCmd) {
|
|
3020
3319
|
throw new Error("Marp CLI not found. Install it with: npm install -D @marp-team/marp-cli");
|
|
3021
3320
|
}
|
|
3022
|
-
|
|
3321
|
+
const finalArgs = addHtmlOption(args);
|
|
3322
|
+
execFileSync(marpCmd, finalArgs, execOptions);
|
|
3023
3323
|
}
|
|
3024
3324
|
function spawnMarp(args, options = {}) {
|
|
3025
3325
|
const { projectDir, ...spawnOptions } = options;
|
|
@@ -3027,7 +3327,8 @@ function spawnMarp(args, options = {}) {
|
|
|
3027
3327
|
if (!marpCmd) {
|
|
3028
3328
|
throw new Error("Marp CLI not found. Install it with: npm install -D @marp-team/marp-cli");
|
|
3029
3329
|
}
|
|
3030
|
-
|
|
3330
|
+
const finalArgs = addHtmlOption(args);
|
|
3331
|
+
return spawn(marpCmd, finalArgs, spawnOptions);
|
|
3031
3332
|
}
|
|
3032
3333
|
var init_marp_runner = __esm({
|
|
3033
3334
|
"src/cli/utils/marp-runner.ts"() {
|
|
@@ -3051,17 +3352,17 @@ __export(screenshot_exports, {
|
|
|
3051
3352
|
generateContactSheet: () => generateContactSheet
|
|
3052
3353
|
});
|
|
3053
3354
|
import { Command as Command4 } from "commander";
|
|
3054
|
-
import { access as
|
|
3055
|
-
import { basename as basename5, dirname as dirname6, extname as extname3, join as
|
|
3355
|
+
import { access as access8, mkdir as mkdir4, readdir as readdir5, rename, unlink as unlink2 } from "fs/promises";
|
|
3356
|
+
import { basename as basename5, dirname as dirname6, extname as extname3, join as join11 } from "path";
|
|
3056
3357
|
import chalk4 from "chalk";
|
|
3057
3358
|
import ora3 from "ora";
|
|
3058
3359
|
import sharp2 from "sharp";
|
|
3059
3360
|
async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
3060
3361
|
const slideStr = slideNumber.toString().padStart(3, "0");
|
|
3061
3362
|
const targetFileName = `${baseName}.${slideStr}.${format}`;
|
|
3062
|
-
const targetPath =
|
|
3363
|
+
const targetPath = join11(outputDir, targetFileName);
|
|
3063
3364
|
try {
|
|
3064
|
-
await
|
|
3365
|
+
await access8(targetPath);
|
|
3065
3366
|
} catch {
|
|
3066
3367
|
return {
|
|
3067
3368
|
success: false,
|
|
@@ -3074,7 +3375,7 @@ async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
|
3074
3375
|
);
|
|
3075
3376
|
for (const file of slideFiles) {
|
|
3076
3377
|
if (file !== targetFileName) {
|
|
3077
|
-
await unlink2(
|
|
3378
|
+
await unlink2(join11(outputDir, file));
|
|
3078
3379
|
}
|
|
3079
3380
|
}
|
|
3080
3381
|
return {
|
|
@@ -3091,8 +3392,8 @@ async function ensureFileExtensions(outputDir, baseName, format) {
|
|
|
3091
3392
|
const renamedFiles = [];
|
|
3092
3393
|
for (const file of files) {
|
|
3093
3394
|
if (file.startsWith(baseName) && slidePattern.test(file)) {
|
|
3094
|
-
const oldPath =
|
|
3095
|
-
const newPath =
|
|
3395
|
+
const oldPath = join11(outputDir, file);
|
|
3396
|
+
const newPath = join11(outputDir, `${file}.${format}`);
|
|
3096
3397
|
await rename(oldPath, newPath);
|
|
3097
3398
|
renamedFiles.push(`${file}.${format}`);
|
|
3098
3399
|
}
|
|
@@ -3186,14 +3487,14 @@ function formatAiOutput(options) {
|
|
|
3186
3487
|
""
|
|
3187
3488
|
];
|
|
3188
3489
|
for (const file of files) {
|
|
3189
|
-
lines.push(` ${
|
|
3490
|
+
lines.push(` ${join11(outputDir, file)}`);
|
|
3190
3491
|
}
|
|
3191
3492
|
lines.push("");
|
|
3192
3493
|
lines.push(`Estimated tokens: ~${totalTokens} (${files.length} ${imageLabel})`);
|
|
3193
3494
|
if (files.length > 0) {
|
|
3194
3495
|
lines.push("");
|
|
3195
3496
|
lines.push("To review in Claude Code:");
|
|
3196
|
-
lines.push(` Read ${
|
|
3497
|
+
lines.push(` Read ${join11(outputDir, files[0])}`);
|
|
3197
3498
|
}
|
|
3198
3499
|
return lines.join("\n");
|
|
3199
3500
|
}
|
|
@@ -3211,7 +3512,7 @@ function buildMarpCommandArgs(markdownPath, outputDir, options) {
|
|
|
3211
3512
|
args.push("--jpeg-quality", String(quality));
|
|
3212
3513
|
}
|
|
3213
3514
|
const mdBaseName = basename5(markdownPath, extname3(markdownPath));
|
|
3214
|
-
const outputPath =
|
|
3515
|
+
const outputPath = join11(outputDir, mdBaseName);
|
|
3215
3516
|
args.push("-o", outputPath);
|
|
3216
3517
|
args.push(markdownPath);
|
|
3217
3518
|
return args;
|
|
@@ -3226,7 +3527,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
3226
3527
|
const spinner = options.verbose ? null : ora3();
|
|
3227
3528
|
const outputDir = options.output || "./screenshots";
|
|
3228
3529
|
try {
|
|
3229
|
-
await
|
|
3530
|
+
await access8(inputPath);
|
|
3230
3531
|
} catch {
|
|
3231
3532
|
const message = `File not found: ${inputPath}`;
|
|
3232
3533
|
console.error(chalk4.red(`Error: ${message}`));
|
|
@@ -3252,7 +3553,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
3252
3553
|
}
|
|
3253
3554
|
spinner?.succeed("Marp CLI found");
|
|
3254
3555
|
try {
|
|
3255
|
-
await
|
|
3556
|
+
await mkdir4(outputDir, { recursive: true });
|
|
3256
3557
|
} catch {
|
|
3257
3558
|
const message = `Failed to create output directory: ${outputDir}`;
|
|
3258
3559
|
console.error(chalk4.red(`Error: ${message}`));
|
|
@@ -3359,7 +3660,7 @@ Error: ${browserError.message}
|
|
|
3359
3660
|
const generatedFiles = allFiles.filter((f) => f.startsWith(mdBaseName) && f.endsWith(`.${actualFormat}`)).sort();
|
|
3360
3661
|
if (generatedFiles.length > 0) {
|
|
3361
3662
|
try {
|
|
3362
|
-
const metadata = await sharp2(
|
|
3663
|
+
const metadata = await sharp2(join11(outputDir, generatedFiles[0])).metadata();
|
|
3363
3664
|
if (metadata.width && metadata.height) {
|
|
3364
3665
|
actualWidth = metadata.width;
|
|
3365
3666
|
actualHeight = metadata.height;
|
|
@@ -3370,10 +3671,10 @@ Error: ${browserError.message}
|
|
|
3370
3671
|
if (options.contactSheet && generatedFiles.length > 0) {
|
|
3371
3672
|
spinner?.start("Generating contact sheet...");
|
|
3372
3673
|
const slides = generatedFiles.map((file, index) => ({
|
|
3373
|
-
path:
|
|
3674
|
+
path: join11(outputDir, file),
|
|
3374
3675
|
index: index + 1
|
|
3375
3676
|
}));
|
|
3376
|
-
const contactSheetPath =
|
|
3677
|
+
const contactSheetPath = join11(outputDir, `${mdBaseName}-contact.png`);
|
|
3377
3678
|
const contactResult = await generateContactSheet(slides, {
|
|
3378
3679
|
outputPath: contactSheetPath,
|
|
3379
3680
|
columns: options.columns || 2,
|
|
@@ -3433,13 +3734,9 @@ init_transformer();
|
|
|
3433
3734
|
init_renderer();
|
|
3434
3735
|
init_pipeline();
|
|
3435
3736
|
|
|
3436
|
-
// src/templates/index.ts
|
|
3437
|
-
init_engine();
|
|
3438
|
-
init_loader();
|
|
3439
|
-
init_validators();
|
|
3440
|
-
|
|
3441
3737
|
// src/index.ts
|
|
3442
|
-
|
|
3738
|
+
init_templates();
|
|
3739
|
+
var VERSION = "0.8.1";
|
|
3443
3740
|
|
|
3444
3741
|
// src/cli/index.ts
|
|
3445
3742
|
init_convert();
|
|
@@ -3451,11 +3748,11 @@ init_registry();
|
|
|
3451
3748
|
init_loader2();
|
|
3452
3749
|
init_convert();
|
|
3453
3750
|
import { Command as Command2 } from "commander";
|
|
3454
|
-
import { access as
|
|
3751
|
+
import { access as access6, readFile as readFile10 } from "fs/promises";
|
|
3455
3752
|
import { dirname as dirname3 } from "path";
|
|
3456
3753
|
import chalk2 from "chalk";
|
|
3457
3754
|
import ora2 from "ora";
|
|
3458
|
-
import { parse as
|
|
3755
|
+
import { parse as parseYaml7, stringify as yamlStringify } from "yaml";
|
|
3459
3756
|
|
|
3460
3757
|
// src/references/index.ts
|
|
3461
3758
|
init_extractor();
|
|
@@ -3696,7 +3993,7 @@ async function executeValidate(inputPath, options) {
|
|
|
3696
3993
|
try {
|
|
3697
3994
|
spinner?.start(`Validating ${inputPath}...`);
|
|
3698
3995
|
try {
|
|
3699
|
-
await
|
|
3996
|
+
await access6(inputPath);
|
|
3700
3997
|
} catch {
|
|
3701
3998
|
spinner?.fail(`File not found: ${inputPath}`);
|
|
3702
3999
|
result.errors.push(`File not found: ${inputPath}`);
|
|
@@ -3705,9 +4002,9 @@ async function executeValidate(inputPath, options) {
|
|
|
3705
4002
|
process.exitCode = ExitCode.FileReadError;
|
|
3706
4003
|
return;
|
|
3707
4004
|
}
|
|
3708
|
-
const content = await
|
|
4005
|
+
const content = await readFile10(inputPath, "utf-8");
|
|
3709
4006
|
try {
|
|
3710
|
-
|
|
4007
|
+
parseYaml7(content);
|
|
3711
4008
|
result.stats.yamlSyntax = true;
|
|
3712
4009
|
} catch (error) {
|
|
3713
4010
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -4028,15 +4325,16 @@ function outputResult(result, options) {
|
|
|
4028
4325
|
}
|
|
4029
4326
|
|
|
4030
4327
|
// src/cli/commands/templates.ts
|
|
4328
|
+
init_templates();
|
|
4329
|
+
init_loader2();
|
|
4330
|
+
init_pipeline();
|
|
4331
|
+
init_convert();
|
|
4031
4332
|
import { Command as Command5 } from "commander";
|
|
4032
4333
|
import chalk5 from "chalk";
|
|
4033
4334
|
import * as yaml2 from "yaml";
|
|
4034
|
-
import { mkdir as
|
|
4035
|
-
import { join as
|
|
4335
|
+
import { mkdir as mkdir5, writeFile as writeFile4, rm as rm2, readdir as readdir6 } from "fs/promises";
|
|
4336
|
+
import { join as join12, basename as basename6 } from "path";
|
|
4036
4337
|
import { tmpdir as tmpdir2 } from "os";
|
|
4037
|
-
init_loader2();
|
|
4038
|
-
init_pipeline();
|
|
4039
|
-
init_convert();
|
|
4040
4338
|
|
|
4041
4339
|
// src/cli/commands/preview.ts
|
|
4042
4340
|
init_pipeline();
|
|
@@ -4044,9 +4342,9 @@ init_loader2();
|
|
|
4044
4342
|
init_convert();
|
|
4045
4343
|
init_marp_runner();
|
|
4046
4344
|
import { Command as Command3 } from "commander";
|
|
4047
|
-
import { access as
|
|
4048
|
-
import { basename as basename4, dirname as dirname5, join as
|
|
4049
|
-
import * as
|
|
4345
|
+
import { access as access7, readdir as readdir4, mkdir as mkdir3, writeFile as writeFile3, readFile as readFile11, rm } from "fs/promises";
|
|
4346
|
+
import { basename as basename4, dirname as dirname5, join as join10, extname as extname2 } from "path";
|
|
4347
|
+
import * as path7 from "path";
|
|
4050
4348
|
import { tmpdir } from "os";
|
|
4051
4349
|
import { createServer } from "http";
|
|
4052
4350
|
import chalk3 from "chalk";
|
|
@@ -4164,7 +4462,7 @@ async function collectSlideInfo(dir, baseName, format) {
|
|
|
4164
4462
|
if (match) {
|
|
4165
4463
|
const index = parseInt(match[1], 10);
|
|
4166
4464
|
slides.push({
|
|
4167
|
-
path:
|
|
4465
|
+
path: join10(dir, file),
|
|
4168
4466
|
title: `Slide ${index}`,
|
|
4169
4467
|
index
|
|
4170
4468
|
});
|
|
@@ -4180,7 +4478,7 @@ async function checkMarpCliAvailable(projectDir) {
|
|
|
4180
4478
|
}
|
|
4181
4479
|
function getTempPreviewDir(inputPath) {
|
|
4182
4480
|
const base = basename4(inputPath, ".yaml");
|
|
4183
|
-
return
|
|
4481
|
+
return join10(tmpdir(), `slide-gen-preview-${base}-${Date.now()}`);
|
|
4184
4482
|
}
|
|
4185
4483
|
function buildMarpCommand(markdownDir, options) {
|
|
4186
4484
|
const parts = ["marp", "--server", "-I", markdownDir];
|
|
@@ -4190,7 +4488,7 @@ function buildMarpCommand(markdownDir, options) {
|
|
|
4190
4488
|
return parts.join(" ");
|
|
4191
4489
|
}
|
|
4192
4490
|
function startStaticServer(baseDir, port, options = {}) {
|
|
4193
|
-
return new Promise((
|
|
4491
|
+
return new Promise((resolve9, reject) => {
|
|
4194
4492
|
const mimeTypes = {
|
|
4195
4493
|
".html": "text/html",
|
|
4196
4494
|
".png": "image/png",
|
|
@@ -4203,8 +4501,8 @@ function startStaticServer(baseDir, port, options = {}) {
|
|
|
4203
4501
|
try {
|
|
4204
4502
|
const urlPath = new URL(req.url || "/", `http://localhost`).pathname;
|
|
4205
4503
|
const requestedPath = urlPath === "/" ? "/index.html" : urlPath;
|
|
4206
|
-
const resolvedBaseDir =
|
|
4207
|
-
const filePath =
|
|
4504
|
+
const resolvedBaseDir = path7.resolve(baseDir);
|
|
4505
|
+
const filePath = path7.resolve(baseDir, "." + requestedPath);
|
|
4208
4506
|
if (!filePath.startsWith(resolvedBaseDir + "/") && filePath !== resolvedBaseDir) {
|
|
4209
4507
|
res.writeHead(403);
|
|
4210
4508
|
res.end("Forbidden");
|
|
@@ -4212,7 +4510,7 @@ function startStaticServer(baseDir, port, options = {}) {
|
|
|
4212
4510
|
}
|
|
4213
4511
|
const ext = extname2(filePath);
|
|
4214
4512
|
const contentType = mimeTypes[ext] || "application/octet-stream";
|
|
4215
|
-
const data = await
|
|
4513
|
+
const data = await readFile11(filePath);
|
|
4216
4514
|
res.writeHead(200, { "Content-Type": contentType });
|
|
4217
4515
|
res.end(data);
|
|
4218
4516
|
} catch {
|
|
@@ -4227,16 +4525,16 @@ function startStaticServer(baseDir, port, options = {}) {
|
|
|
4227
4525
|
const prefix = options.messagePrefix ?? "Server";
|
|
4228
4526
|
console.log(`${prefix} running at ${chalk3.cyan(url)}`);
|
|
4229
4527
|
}
|
|
4230
|
-
|
|
4528
|
+
resolve9(server);
|
|
4231
4529
|
});
|
|
4232
4530
|
});
|
|
4233
4531
|
}
|
|
4234
4532
|
async function executeGalleryPreview(inputPath, options) {
|
|
4235
4533
|
const errors = [];
|
|
4236
4534
|
const port = Number(options.port) || 8080;
|
|
4237
|
-
const galleryDir =
|
|
4535
|
+
const galleryDir = join10(tmpdir(), `slide-gen-gallery-${Date.now()}`);
|
|
4238
4536
|
try {
|
|
4239
|
-
await
|
|
4537
|
+
await access7(inputPath);
|
|
4240
4538
|
} catch {
|
|
4241
4539
|
console.error(chalk3.red(`Error: File not found: ${inputPath}`));
|
|
4242
4540
|
errors.push(`File not found: ${inputPath}`);
|
|
@@ -4257,7 +4555,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
4257
4555
|
return { success: false, errors };
|
|
4258
4556
|
}
|
|
4259
4557
|
console.log(chalk3.green("\u2713") + " Marp CLI found");
|
|
4260
|
-
await
|
|
4558
|
+
await mkdir3(galleryDir, { recursive: true });
|
|
4261
4559
|
const configLoader = new ConfigLoader();
|
|
4262
4560
|
let configPath = options.config;
|
|
4263
4561
|
if (!configPath) {
|
|
@@ -4276,7 +4574,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
4276
4574
|
await rm(galleryDir, { recursive: true, force: true });
|
|
4277
4575
|
return { success: false, errors };
|
|
4278
4576
|
}
|
|
4279
|
-
const tempMdPath =
|
|
4577
|
+
const tempMdPath = join10(galleryDir, "slides.md");
|
|
4280
4578
|
console.log(`Converting ${chalk3.cyan(inputPath)}...`);
|
|
4281
4579
|
try {
|
|
4282
4580
|
await pipeline.runWithResult(inputPath, { outputPath: tempMdPath });
|
|
@@ -4317,7 +4615,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
4317
4615
|
path: basename4(s.path)
|
|
4318
4616
|
}));
|
|
4319
4617
|
const galleryHtml = generateGalleryHtml(relativeSlides);
|
|
4320
|
-
await
|
|
4618
|
+
await writeFile3(join10(galleryDir, "index.html"), galleryHtml);
|
|
4321
4619
|
console.log(`
|
|
4322
4620
|
Starting gallery server on port ${chalk3.cyan(port)}...`);
|
|
4323
4621
|
let server;
|
|
@@ -4359,11 +4657,11 @@ Showing ${slides.length} slides in gallery mode`);
|
|
|
4359
4657
|
};
|
|
4360
4658
|
process.on("SIGINT", signalHandler);
|
|
4361
4659
|
process.on("SIGTERM", signalHandler);
|
|
4362
|
-
await new Promise((
|
|
4660
|
+
await new Promise((resolve9) => {
|
|
4363
4661
|
if (options.signal) {
|
|
4364
|
-
options.signal.addEventListener("abort", () =>
|
|
4662
|
+
options.signal.addEventListener("abort", () => resolve9());
|
|
4365
4663
|
}
|
|
4366
|
-
server.on("close", () =>
|
|
4664
|
+
server.on("close", () => resolve9());
|
|
4367
4665
|
});
|
|
4368
4666
|
return { success: true, errors };
|
|
4369
4667
|
}
|
|
@@ -4381,7 +4679,7 @@ async function executePreview(inputPath, options) {
|
|
|
4381
4679
|
const port = Number(options.port) || 8080;
|
|
4382
4680
|
if (options.signal?.aborted) {
|
|
4383
4681
|
try {
|
|
4384
|
-
await
|
|
4682
|
+
await access7(inputPath);
|
|
4385
4683
|
} catch {
|
|
4386
4684
|
errors.push(`File not found: ${inputPath}`);
|
|
4387
4685
|
return { success: false, errors };
|
|
@@ -4389,7 +4687,7 @@ async function executePreview(inputPath, options) {
|
|
|
4389
4687
|
return { success: true, errors };
|
|
4390
4688
|
}
|
|
4391
4689
|
try {
|
|
4392
|
-
await
|
|
4690
|
+
await access7(inputPath);
|
|
4393
4691
|
} catch {
|
|
4394
4692
|
console.error(chalk3.red(`Error: File not found: ${inputPath}`));
|
|
4395
4693
|
errors.push(`File not found: ${inputPath}`);
|
|
@@ -4427,8 +4725,8 @@ async function executePreview(inputPath, options) {
|
|
|
4427
4725
|
return { success: false, errors };
|
|
4428
4726
|
}
|
|
4429
4727
|
const tempDir = getTempPreviewDir(inputPath);
|
|
4430
|
-
await
|
|
4431
|
-
const tempMarkdownPath =
|
|
4728
|
+
await mkdir3(tempDir, { recursive: true });
|
|
4729
|
+
const tempMarkdownPath = join10(tempDir, "slides.md");
|
|
4432
4730
|
console.log(`Converting ${chalk3.cyan(inputPath)}...`);
|
|
4433
4731
|
try {
|
|
4434
4732
|
await pipeline.runWithResult(inputPath, { outputPath: tempMarkdownPath });
|
|
@@ -4514,13 +4812,13 @@ Watching ${chalk3.cyan(inputPath)} for changes...`);
|
|
|
4514
4812
|
};
|
|
4515
4813
|
process.on("SIGINT", signalHandler);
|
|
4516
4814
|
process.on("SIGTERM", signalHandler);
|
|
4517
|
-
await new Promise((
|
|
4815
|
+
await new Promise((resolve9) => {
|
|
4518
4816
|
marpProcess.on("exit", () => {
|
|
4519
4817
|
cleanup();
|
|
4520
|
-
|
|
4818
|
+
resolve9();
|
|
4521
4819
|
});
|
|
4522
4820
|
if (options.signal) {
|
|
4523
|
-
options.signal.addEventListener("abort", () =>
|
|
4821
|
+
options.signal.addEventListener("abort", () => resolve9());
|
|
4524
4822
|
}
|
|
4525
4823
|
});
|
|
4526
4824
|
return {
|
|
@@ -4944,7 +5242,7 @@ async function executeTemplatePreview(name, options) {
|
|
|
4944
5242
|
return;
|
|
4945
5243
|
}
|
|
4946
5244
|
const port = Number(options.port) || 8080;
|
|
4947
|
-
const previewDir =
|
|
5245
|
+
const previewDir = join12(tmpdir2(), `slide-gen-template-preview-${Date.now()}`);
|
|
4948
5246
|
console.log("Checking for Marp CLI...");
|
|
4949
5247
|
const marpAvailable = await checkMarpCliAvailable();
|
|
4950
5248
|
if (!marpAvailable) {
|
|
@@ -4981,7 +5279,7 @@ async function executeTemplatePreview(name, options) {
|
|
|
4981
5279
|
return;
|
|
4982
5280
|
}
|
|
4983
5281
|
console.log(`Found ${templates.length} template(s) to preview`);
|
|
4984
|
-
await
|
|
5282
|
+
await mkdir5(previewDir, { recursive: true });
|
|
4985
5283
|
console.log("Initializing pipeline...");
|
|
4986
5284
|
const pipeline = new Pipeline(config);
|
|
4987
5285
|
try {
|
|
@@ -4997,9 +5295,9 @@ async function executeTemplatePreview(name, options) {
|
|
|
4997
5295
|
for (const template of templates) {
|
|
4998
5296
|
console.log(`Processing template: ${chalk5.cyan(template.name)}...`);
|
|
4999
5297
|
const sampleYaml = generateSampleYaml(template);
|
|
5000
|
-
const yamlPath =
|
|
5001
|
-
await
|
|
5002
|
-
const mdPath =
|
|
5298
|
+
const yamlPath = join12(previewDir, `${template.name}.yaml`);
|
|
5299
|
+
await writeFile4(yamlPath, sampleYaml);
|
|
5300
|
+
const mdPath = join12(previewDir, `${template.name}.md`);
|
|
5003
5301
|
try {
|
|
5004
5302
|
await pipeline.runWithResult(yamlPath, { outputPath: mdPath });
|
|
5005
5303
|
} catch (error) {
|
|
@@ -5032,7 +5330,7 @@ async function executeTemplatePreview(name, options) {
|
|
|
5032
5330
|
return;
|
|
5033
5331
|
}
|
|
5034
5332
|
const previewHtml = generateTemplatePreviewHtml(templatePreviews);
|
|
5035
|
-
await
|
|
5333
|
+
await writeFile4(join12(previewDir, "index.html"), previewHtml);
|
|
5036
5334
|
console.log(`
|
|
5037
5335
|
Starting preview server on port ${chalk5.cyan(port)}...`);
|
|
5038
5336
|
let server;
|
|
@@ -5069,8 +5367,8 @@ Showing ${templatePreviews.length} template preview(s)`);
|
|
|
5069
5367
|
};
|
|
5070
5368
|
process.on("SIGINT", signalHandler);
|
|
5071
5369
|
process.on("SIGTERM", signalHandler);
|
|
5072
|
-
await new Promise((
|
|
5073
|
-
server.on("close", () =>
|
|
5370
|
+
await new Promise((resolve9) => {
|
|
5371
|
+
server.on("close", () => resolve9());
|
|
5074
5372
|
});
|
|
5075
5373
|
}
|
|
5076
5374
|
function createScreenshotSubcommand() {
|
|
@@ -5114,7 +5412,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5114
5412
|
return { success: true, errors: [], files: [] };
|
|
5115
5413
|
}
|
|
5116
5414
|
const outputDir = options.output || "./template-screenshots";
|
|
5117
|
-
await
|
|
5415
|
+
await mkdir5(outputDir, { recursive: true });
|
|
5118
5416
|
const configLoader = new ConfigLoader();
|
|
5119
5417
|
const configPath = options.config || await configLoader.findConfig(process.cwd());
|
|
5120
5418
|
const config = await configLoader.load(configPath);
|
|
@@ -5127,8 +5425,8 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5127
5425
|
process.exitCode = ExitCode.GeneralError;
|
|
5128
5426
|
return { success: false, errors: [message] };
|
|
5129
5427
|
}
|
|
5130
|
-
const tempDir =
|
|
5131
|
-
await
|
|
5428
|
+
const tempDir = join12(tmpdir2(), `slide-gen-template-screenshot-${Date.now()}`);
|
|
5429
|
+
await mkdir5(tempDir, { recursive: true });
|
|
5132
5430
|
const isAiFormat = options.format === "ai";
|
|
5133
5431
|
const imageFormat = isAiFormat ? "jpeg" : options.format || "png";
|
|
5134
5432
|
const imageWidth = isAiFormat ? 640 : options.width || 1280;
|
|
@@ -5139,9 +5437,9 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5139
5437
|
console.log(`Processing: ${template.name}`);
|
|
5140
5438
|
}
|
|
5141
5439
|
const sampleYaml = generateSampleYaml(template);
|
|
5142
|
-
const yamlPath =
|
|
5143
|
-
await
|
|
5144
|
-
const mdPath =
|
|
5440
|
+
const yamlPath = join12(tempDir, `${template.name}.yaml`);
|
|
5441
|
+
await writeFile4(yamlPath, sampleYaml);
|
|
5442
|
+
const mdPath = join12(tempDir, `${template.name}.md`);
|
|
5145
5443
|
try {
|
|
5146
5444
|
await pipeline.runWithResult(yamlPath, { outputPath: mdPath });
|
|
5147
5445
|
} catch (error) {
|
|
@@ -5158,7 +5456,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5158
5456
|
if (imageFormat === "jpeg") {
|
|
5159
5457
|
marpArgs.push("--jpeg-quality", String(options.quality || 80));
|
|
5160
5458
|
}
|
|
5161
|
-
const outputBasePath =
|
|
5459
|
+
const outputBasePath = join12(tempDir, template.name);
|
|
5162
5460
|
marpArgs.push("-o", outputBasePath);
|
|
5163
5461
|
marpArgs.push(mdPath);
|
|
5164
5462
|
try {
|
|
@@ -5178,7 +5476,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5178
5476
|
(f) => f.startsWith(template.name) && f.endsWith(`.${imageFormat}`)
|
|
5179
5477
|
);
|
|
5180
5478
|
for (const slideFile of slideFiles) {
|
|
5181
|
-
const sourceFile =
|
|
5479
|
+
const sourceFile = join12(tempDir, slideFile);
|
|
5182
5480
|
const match = slideFile.match(/\.(\d{3})\.\w+$/);
|
|
5183
5481
|
let targetName;
|
|
5184
5482
|
if (slideFiles.length === 1 || !match) {
|
|
@@ -5186,7 +5484,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5186
5484
|
} else {
|
|
5187
5485
|
targetName = slideFile;
|
|
5188
5486
|
}
|
|
5189
|
-
const targetFile =
|
|
5487
|
+
const targetFile = join12(outputDir, targetName);
|
|
5190
5488
|
const { copyFile: fsCopyFile } = await import("fs/promises");
|
|
5191
5489
|
await fsCopyFile(sourceFile, targetFile);
|
|
5192
5490
|
generatedFiles.push(targetName);
|
|
@@ -5198,10 +5496,10 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5198
5496
|
console.log("Generating contact sheet...");
|
|
5199
5497
|
const { generateContactSheet: genContactSheet } = await Promise.resolve().then(() => (init_screenshot(), screenshot_exports));
|
|
5200
5498
|
const slides = generatedFiles.map((file, index) => ({
|
|
5201
|
-
path:
|
|
5499
|
+
path: join12(outputDir, file),
|
|
5202
5500
|
index: index + 1
|
|
5203
5501
|
}));
|
|
5204
|
-
const contactSheetPath =
|
|
5502
|
+
const contactSheetPath = join12(outputDir, `templates-contact.${imageFormat === "jpeg" ? "jpeg" : "png"}`);
|
|
5205
5503
|
const contactResult = await genContactSheet(slides, {
|
|
5206
5504
|
outputPath: contactSheetPath,
|
|
5207
5505
|
columns: options.columns || 3,
|
|
@@ -5224,7 +5522,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5224
5522
|
console.log("Screenshots saved (AI-optimized):");
|
|
5225
5523
|
console.log("");
|
|
5226
5524
|
for (const file of generatedFiles) {
|
|
5227
|
-
console.log(` ${
|
|
5525
|
+
console.log(` ${join12(outputDir, file)}`);
|
|
5228
5526
|
}
|
|
5229
5527
|
console.log("");
|
|
5230
5528
|
console.log(`Estimated tokens: ~${totalTokens} (${generatedFiles.length} images)`);
|
|
@@ -5252,194 +5550,14 @@ function createTemplatesCommand() {
|
|
|
5252
5550
|
// src/cli/commands/icons.ts
|
|
5253
5551
|
init_registry();
|
|
5254
5552
|
init_resolver();
|
|
5553
|
+
init_fetcher();
|
|
5554
|
+
init_loader2();
|
|
5555
|
+
init_convert();
|
|
5255
5556
|
import { Command as Command6 } from "commander";
|
|
5256
5557
|
import chalk6 from "chalk";
|
|
5257
5558
|
import * as fs8 from "fs/promises";
|
|
5258
5559
|
import * as path8 from "path";
|
|
5259
5560
|
import { parse as parseYaml8, stringify as stringifyYaml2 } from "yaml";
|
|
5260
|
-
|
|
5261
|
-
// src/icons/fetcher.ts
|
|
5262
|
-
import * as fs7 from "fs/promises";
|
|
5263
|
-
import * as path7 from "path";
|
|
5264
|
-
import { stringify as stringifyYaml, parse as parseYaml7 } from "yaml";
|
|
5265
|
-
var ICON_SOURCES = {
|
|
5266
|
-
health: { set: "healthicons", license: "MIT", external: true },
|
|
5267
|
-
ms: { set: "material-symbols", license: "Apache-2.0", external: true },
|
|
5268
|
-
hero: { set: "heroicons", license: "MIT", external: true },
|
|
5269
|
-
mi: { set: "material-icons", license: "Apache-2.0", external: false },
|
|
5270
|
-
mdi: { set: "mdi", license: "Apache-2.0", external: true },
|
|
5271
|
-
iconify: { set: "iconify", license: "Various", external: true }
|
|
5272
|
-
};
|
|
5273
|
-
function isExternalSource(prefix) {
|
|
5274
|
-
return ICON_SOURCES[prefix]?.external ?? false;
|
|
5275
|
-
}
|
|
5276
|
-
var VALID_ICON_NAME = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/i;
|
|
5277
|
-
var VALID_ICON_NAME_WITH_PATH = /^[a-z0-9][a-z0-9-/]*[a-z0-9]$|^[a-z0-9]$/i;
|
|
5278
|
-
function isValidIconName(name, allowPath = false) {
|
|
5279
|
-
if (!name || name.length > 100) {
|
|
5280
|
-
return false;
|
|
5281
|
-
}
|
|
5282
|
-
if (name.includes("..") || name.startsWith("/") || name.endsWith("/")) {
|
|
5283
|
-
return false;
|
|
5284
|
-
}
|
|
5285
|
-
const pattern = allowPath ? VALID_ICON_NAME_WITH_PATH : VALID_ICON_NAME;
|
|
5286
|
-
return pattern.test(name);
|
|
5287
|
-
}
|
|
5288
|
-
var IconFetcher = class {
|
|
5289
|
-
fetchedDir;
|
|
5290
|
-
saveLocally;
|
|
5291
|
-
timeoutMs;
|
|
5292
|
-
constructor(options = {}) {
|
|
5293
|
-
this.fetchedDir = options.fetchedDir ?? "icons/fetched";
|
|
5294
|
-
this.saveLocally = options.saveLocally ?? true;
|
|
5295
|
-
this.timeoutMs = options.timeoutMs ?? 1e4;
|
|
5296
|
-
}
|
|
5297
|
-
/**
|
|
5298
|
-
* Parse an icon reference string (e.g., "health:stethoscope")
|
|
5299
|
-
* Returns null if the format is invalid or contains unsafe characters
|
|
5300
|
-
*/
|
|
5301
|
-
parseReference(iconRef) {
|
|
5302
|
-
const colonIndex = iconRef.indexOf(":");
|
|
5303
|
-
if (colonIndex === -1) {
|
|
5304
|
-
return null;
|
|
5305
|
-
}
|
|
5306
|
-
const prefix = iconRef.substring(0, colonIndex);
|
|
5307
|
-
const name = iconRef.substring(colonIndex + 1);
|
|
5308
|
-
if (!/^[a-z0-9]+$/i.test(prefix)) {
|
|
5309
|
-
return null;
|
|
5310
|
-
}
|
|
5311
|
-
if (!isValidIconName(name)) {
|
|
5312
|
-
return null;
|
|
5313
|
-
}
|
|
5314
|
-
return { prefix, name };
|
|
5315
|
-
}
|
|
5316
|
-
/**
|
|
5317
|
-
* Get the Iconify set name for a prefix
|
|
5318
|
-
*/
|
|
5319
|
-
getIconifySet(prefix) {
|
|
5320
|
-
return ICON_SOURCES[prefix]?.set ?? prefix;
|
|
5321
|
-
}
|
|
5322
|
-
/**
|
|
5323
|
-
* Get the local file path for an icon reference
|
|
5324
|
-
*/
|
|
5325
|
-
getLocalPath(iconRef) {
|
|
5326
|
-
const parsed = this.parseReference(iconRef);
|
|
5327
|
-
if (!parsed) {
|
|
5328
|
-
throw new Error(`Invalid icon reference: ${iconRef}`);
|
|
5329
|
-
}
|
|
5330
|
-
const setDir = this.getIconifySet(parsed.prefix);
|
|
5331
|
-
return path7.join(this.fetchedDir, setDir, `${parsed.name}.svg`);
|
|
5332
|
-
}
|
|
5333
|
-
/**
|
|
5334
|
-
* Check if an icon exists locally
|
|
5335
|
-
*/
|
|
5336
|
-
async existsLocally(iconRef) {
|
|
5337
|
-
const localPath = this.getLocalPath(iconRef);
|
|
5338
|
-
try {
|
|
5339
|
-
await fs7.access(localPath);
|
|
5340
|
-
return true;
|
|
5341
|
-
} catch {
|
|
5342
|
-
return false;
|
|
5343
|
-
}
|
|
5344
|
-
}
|
|
5345
|
-
/**
|
|
5346
|
-
* Build the Iconify API URL for an icon
|
|
5347
|
-
*/
|
|
5348
|
-
buildUrl(prefix, name) {
|
|
5349
|
-
const set = this.getIconifySet(prefix);
|
|
5350
|
-
return `https://api.iconify.design/${set}/${name}.svg`;
|
|
5351
|
-
}
|
|
5352
|
-
/**
|
|
5353
|
-
* Resolve an icon reference (local first, then fetch)
|
|
5354
|
-
*/
|
|
5355
|
-
async resolve(iconRef) {
|
|
5356
|
-
if (await this.existsLocally(iconRef)) {
|
|
5357
|
-
const localPath = this.getLocalPath(iconRef);
|
|
5358
|
-
return await fs7.readFile(localPath, "utf-8");
|
|
5359
|
-
}
|
|
5360
|
-
return await this.fetchAndSave(iconRef);
|
|
5361
|
-
}
|
|
5362
|
-
/**
|
|
5363
|
-
* Fetch an icon from external source and save locally
|
|
5364
|
-
*/
|
|
5365
|
-
async fetchAndSave(iconRef) {
|
|
5366
|
-
const parsed = this.parseReference(iconRef);
|
|
5367
|
-
if (!parsed) {
|
|
5368
|
-
throw new Error(`Invalid icon reference: ${iconRef}`);
|
|
5369
|
-
}
|
|
5370
|
-
const url = this.buildUrl(parsed.prefix, parsed.name);
|
|
5371
|
-
const controller = new AbortController();
|
|
5372
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
5373
|
-
let response;
|
|
5374
|
-
try {
|
|
5375
|
-
response = await fetch(url, { signal: controller.signal });
|
|
5376
|
-
} catch (error) {
|
|
5377
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
5378
|
-
throw new Error(`Fetch timeout: ${iconRef} (exceeded ${this.timeoutMs}ms)`);
|
|
5379
|
-
}
|
|
5380
|
-
throw error;
|
|
5381
|
-
} finally {
|
|
5382
|
-
clearTimeout(timeoutId);
|
|
5383
|
-
}
|
|
5384
|
-
if (!response.ok) {
|
|
5385
|
-
throw new Error(`Icon not found: ${iconRef} (HTTP ${response.status})`);
|
|
5386
|
-
}
|
|
5387
|
-
const svg = await response.text();
|
|
5388
|
-
if (this.saveLocally) {
|
|
5389
|
-
await this.saveLocallyInternal(parsed.prefix, parsed.name, svg, url);
|
|
5390
|
-
}
|
|
5391
|
-
return svg;
|
|
5392
|
-
}
|
|
5393
|
-
/**
|
|
5394
|
-
* Save icon to local filesystem
|
|
5395
|
-
*/
|
|
5396
|
-
async saveLocallyInternal(prefix, name, svg, sourceUrl) {
|
|
5397
|
-
const setDir = this.getIconifySet(prefix);
|
|
5398
|
-
const dirPath = path7.join(this.fetchedDir, setDir);
|
|
5399
|
-
const filePath = path7.join(dirPath, `${name}.svg`);
|
|
5400
|
-
await fs7.mkdir(dirPath, { recursive: true });
|
|
5401
|
-
await fs7.writeFile(filePath, svg, "utf-8");
|
|
5402
|
-
await this.updateSourcesYaml(setDir, name, sourceUrl);
|
|
5403
|
-
}
|
|
5404
|
-
/**
|
|
5405
|
-
* Update _sources.yaml with source information
|
|
5406
|
-
*/
|
|
5407
|
-
async updateSourcesYaml(setDir, name, sourceUrl) {
|
|
5408
|
-
const sourcesPath = path7.join(this.fetchedDir, "_sources.yaml");
|
|
5409
|
-
let sources = {};
|
|
5410
|
-
try {
|
|
5411
|
-
const content = await fs7.readFile(sourcesPath, "utf-8");
|
|
5412
|
-
sources = parseYaml7(content) ?? {};
|
|
5413
|
-
} catch {
|
|
5414
|
-
}
|
|
5415
|
-
const key = `${setDir}/${name}.svg`;
|
|
5416
|
-
sources[key] = {
|
|
5417
|
-
source: sourceUrl,
|
|
5418
|
-
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5419
|
-
license: this.getLicense(setDir)
|
|
5420
|
-
};
|
|
5421
|
-
const header = `# Fetched icon sources
|
|
5422
|
-
# This file tracks where icons were fetched from for traceability
|
|
5423
|
-
|
|
5424
|
-
`;
|
|
5425
|
-
await fs7.writeFile(sourcesPath, header + stringifyYaml(sources), "utf-8");
|
|
5426
|
-
}
|
|
5427
|
-
/**
|
|
5428
|
-
* Get the license for an icon set
|
|
5429
|
-
*/
|
|
5430
|
-
getLicense(setDir) {
|
|
5431
|
-
for (const config of Object.values(ICON_SOURCES)) {
|
|
5432
|
-
if (config.set === setDir) {
|
|
5433
|
-
return config.license;
|
|
5434
|
-
}
|
|
5435
|
-
}
|
|
5436
|
-
return "Unknown";
|
|
5437
|
-
}
|
|
5438
|
-
};
|
|
5439
|
-
|
|
5440
|
-
// src/cli/commands/icons.ts
|
|
5441
|
-
init_loader2();
|
|
5442
|
-
init_convert();
|
|
5443
5561
|
function extractIconReferences(presentation) {
|
|
5444
5562
|
const icons = /* @__PURE__ */ new Set();
|
|
5445
5563
|
function extractFromValue(value) {
|
|
@@ -6040,7 +6158,7 @@ import { mkdir as mkdir8, writeFile as writeFile7, access as access11, readdir a
|
|
|
6040
6158
|
import { existsSync as existsSync2 } from "fs";
|
|
6041
6159
|
import { execSync, spawnSync } from "child_process";
|
|
6042
6160
|
import { createInterface } from "readline";
|
|
6043
|
-
import { basename as basename8, dirname as dirname8, join as join17, resolve as
|
|
6161
|
+
import { basename as basename8, dirname as dirname8, join as join17, resolve as resolve7, sep } from "path";
|
|
6044
6162
|
import { fileURLToPath } from "url";
|
|
6045
6163
|
import chalk7 from "chalk";
|
|
6046
6164
|
import ora4 from "ora";
|
|
@@ -7014,6 +7132,73 @@ Take new screenshots and verify improvements.
|
|
|
7014
7132
|
7. \`Read ./screenshots/presentation.003.jpeg\`
|
|
7015
7133
|
8. Verify fix, move to next slide
|
|
7016
7134
|
|
|
7135
|
+
## Custom Template Creation
|
|
7136
|
+
|
|
7137
|
+
When creating or modifying templates, follow these critical rules.
|
|
7138
|
+
|
|
7139
|
+
### Critical Rules
|
|
7140
|
+
|
|
7141
|
+
#### 1. CSS Selectors (Marp Scoping)
|
|
7142
|
+
|
|
7143
|
+
When using \`<!-- _class: foo -->\`:
|
|
7144
|
+
- The class is added to \`<section>\` element itself
|
|
7145
|
+
- Use \`section.foo\` when targeting the \`<section>\` element itself
|
|
7146
|
+
|
|
7147
|
+
\`\`\`yaml
|
|
7148
|
+
# Correct
|
|
7149
|
+
output: |
|
|
7150
|
+
<!-- _class: my-slide -->
|
|
7151
|
+
<div class="container">...</div>
|
|
7152
|
+
|
|
7153
|
+
css: |
|
|
7154
|
+
section.my-slide .container { display: flex; }
|
|
7155
|
+
\`\`\`
|
|
7156
|
+
|
|
7157
|
+
\`\`\`yaml
|
|
7158
|
+
# WRONG - This does not target the section element itself
|
|
7159
|
+
css: |
|
|
7160
|
+
.my-slide .container { display: flex; }
|
|
7161
|
+
\`\`\`
|
|
7162
|
+
|
|
7163
|
+
Note: \`.my-slide .container\` is valid for descendants inside the section, but it does not target the \`<section>\` element itself.
|
|
7164
|
+
|
|
7165
|
+
#### 2. HTML + Markdown (CommonMark)
|
|
7166
|
+
|
|
7167
|
+
Markdown inside HTML is only parsed with blank lines:
|
|
7168
|
+
|
|
7169
|
+
\`\`\`yaml
|
|
7170
|
+
# Correct - blank line after <div>
|
|
7171
|
+
output: |
|
|
7172
|
+
<div class="container">
|
|
7173
|
+
|
|
7174
|
+
## Heading works
|
|
7175
|
+
- List works
|
|
7176
|
+
|
|
7177
|
+
</div>
|
|
7178
|
+
\`\`\`
|
|
7179
|
+
|
|
7180
|
+
\`\`\`yaml
|
|
7181
|
+
# WRONG - no blank line, Markdown won't parse
|
|
7182
|
+
output: |
|
|
7183
|
+
<div class="container">
|
|
7184
|
+
## This is plain text
|
|
7185
|
+
- Not a list
|
|
7186
|
+
</div>
|
|
7187
|
+
\`\`\`
|
|
7188
|
+
|
|
7189
|
+
### Visual Regression Test (Required)
|
|
7190
|
+
|
|
7191
|
+
After creating/modifying templates, ALWAYS verify with screenshots:
|
|
7192
|
+
|
|
7193
|
+
\`\`\`bash
|
|
7194
|
+
slide-gen templates screenshot <template-name> --format png -o /tmp/test
|
|
7195
|
+
\`\`\`
|
|
7196
|
+
|
|
7197
|
+
**Check that:**
|
|
7198
|
+
- Layout is correct (columns, grids, flexbox)
|
|
7199
|
+
- Markdown is parsed (headings, lists rendered properly)
|
|
7200
|
+
- CSS is not silently failing
|
|
7201
|
+
|
|
7017
7202
|
## Reference Management
|
|
7018
7203
|
|
|
7019
7204
|
For academic presentations, manage citations and references:
|
|
@@ -7312,6 +7497,92 @@ Image with text side by side.
|
|
|
7312
7497
|
- \`items\` or \`text\`: Content (required)
|
|
7313
7498
|
|
|
7314
7499
|
## Run \`slide-gen templates list --format llm\` for full list.
|
|
7500
|
+
|
|
7501
|
+
---
|
|
7502
|
+
|
|
7503
|
+
## Creating Custom Templates
|
|
7504
|
+
|
|
7505
|
+
### Template File Structure
|
|
7506
|
+
|
|
7507
|
+
\`\`\`yaml
|
|
7508
|
+
name: my-template
|
|
7509
|
+
description: "Description for LLM"
|
|
7510
|
+
category: custom
|
|
7511
|
+
|
|
7512
|
+
schema:
|
|
7513
|
+
type: object
|
|
7514
|
+
required: [title]
|
|
7515
|
+
properties:
|
|
7516
|
+
title: { type: string }
|
|
7517
|
+
content: { type: string }
|
|
7518
|
+
|
|
7519
|
+
example:
|
|
7520
|
+
title: "Sample"
|
|
7521
|
+
content: "Sample content"
|
|
7522
|
+
|
|
7523
|
+
output: |
|
|
7524
|
+
---
|
|
7525
|
+
<!-- _class: my-template -->
|
|
7526
|
+
|
|
7527
|
+
# {{ title }}
|
|
7528
|
+
|
|
7529
|
+
<div class="content">
|
|
7530
|
+
|
|
7531
|
+
{{ content }}
|
|
7532
|
+
|
|
7533
|
+
</div>
|
|
7534
|
+
|
|
7535
|
+
css: |
|
|
7536
|
+
section.my-template .content {
|
|
7537
|
+
padding: 1em;
|
|
7538
|
+
}
|
|
7539
|
+
\`\`\`
|
|
7540
|
+
|
|
7541
|
+
### Critical CSS Rule: Marp Scoping
|
|
7542
|
+
|
|
7543
|
+
When using \`<!-- _class: foo -->\`, Marp adds class to the \`<section>\` element.
|
|
7544
|
+
|
|
7545
|
+
**Correct (targeting the section element):**
|
|
7546
|
+
\`\`\`css
|
|
7547
|
+
section.foo .child { ... }
|
|
7548
|
+
\`\`\`
|
|
7549
|
+
|
|
7550
|
+
**Wrong (for targeting the section element):**
|
|
7551
|
+
\`\`\`css
|
|
7552
|
+
.foo .child { ... }
|
|
7553
|
+
\`\`\`
|
|
7554
|
+
|
|
7555
|
+
Note: \`.foo .child\` is valid for descendants inside the section, but it does not target the \`<section>\` element itself.
|
|
7556
|
+
|
|
7557
|
+
### Critical HTML Rule: CommonMark
|
|
7558
|
+
|
|
7559
|
+
Markdown inside HTML only parses with blank lines after tags:
|
|
7560
|
+
|
|
7561
|
+
**Correct:**
|
|
7562
|
+
\`\`\`html
|
|
7563
|
+
<div>
|
|
7564
|
+
|
|
7565
|
+
## Heading works
|
|
7566
|
+
|
|
7567
|
+
</div>
|
|
7568
|
+
\`\`\`
|
|
7569
|
+
|
|
7570
|
+
**Wrong:**
|
|
7571
|
+
\`\`\`html
|
|
7572
|
+
<div>
|
|
7573
|
+
## Plain text, not heading
|
|
7574
|
+
</div>
|
|
7575
|
+
\`\`\`
|
|
7576
|
+
|
|
7577
|
+
### Visual Verification
|
|
7578
|
+
|
|
7579
|
+
Always test templates with screenshots:
|
|
7580
|
+
|
|
7581
|
+
\`\`\`bash
|
|
7582
|
+
slide-gen templates screenshot <template-name> --format png -o /tmp/test
|
|
7583
|
+
\`\`\`
|
|
7584
|
+
|
|
7585
|
+
Check: Layout, Markdown parsing, CSS application.
|
|
7315
7586
|
`;
|
|
7316
7587
|
}
|
|
7317
7588
|
|
|
@@ -7989,7 +8260,8 @@ slide-gen validate presentation.yaml
|
|
|
7989
8260
|
// src/cli/commands/init.ts
|
|
7990
8261
|
function getPackageRoot() {
|
|
7991
8262
|
const __dirname = dirname8(fileURLToPath(import.meta.url));
|
|
7992
|
-
|
|
8263
|
+
const isInSourceDir = __dirname.endsWith(`${sep}src${sep}cli${sep}commands`) || __dirname.endsWith("/src/cli/commands");
|
|
8264
|
+
if (isInSourceDir) {
|
|
7993
8265
|
return join17(__dirname, "..", "..", "..");
|
|
7994
8266
|
}
|
|
7995
8267
|
return join17(__dirname, "..", "..");
|
|
@@ -8001,7 +8273,7 @@ function createInitCommand() {
|
|
|
8001
8273
|
}
|
|
8002
8274
|
async function executeInit(directory, options) {
|
|
8003
8275
|
const spinner = ora4();
|
|
8004
|
-
const targetDir =
|
|
8276
|
+
const targetDir = resolve7(directory);
|
|
8005
8277
|
const includeExamples = options.examples !== false;
|
|
8006
8278
|
const includeAiConfig = options.aiConfig !== false;
|
|
8007
8279
|
const includeSources = options.sources !== false;
|
|
@@ -8029,6 +8301,13 @@ async function executeInit(directory, options) {
|
|
|
8029
8301
|
const sourceIconsRegistry = join17(packageRoot, "icons", "registry.yaml");
|
|
8030
8302
|
const targetIconsRegistry = join17(targetDir, "icons", "registry.yaml");
|
|
8031
8303
|
await copyFileIfNotExists(sourceIconsRegistry, targetIconsRegistry);
|
|
8304
|
+
const sourceIconsFetched = join17(packageRoot, "icons", "fetched");
|
|
8305
|
+
const targetIconsFetched = join17(targetDir, "icons", "fetched");
|
|
8306
|
+
try {
|
|
8307
|
+
await access11(targetIconsFetched);
|
|
8308
|
+
} catch {
|
|
8309
|
+
await cp(sourceIconsFetched, targetIconsFetched, { recursive: true });
|
|
8310
|
+
}
|
|
8032
8311
|
const sourceDefaultTheme = join17(packageRoot, "themes", "default.css");
|
|
8033
8312
|
const targetDefaultTheme = join17(targetDir, "themes", "default.css");
|
|
8034
8313
|
await copyFileIfNotExists(sourceDefaultTheme, targetDefaultTheme);
|
|
@@ -8050,11 +8329,11 @@ async function executeInit(directory, options) {
|
|
|
8050
8329
|
await sourcesManager.init({
|
|
8051
8330
|
name: "Untitled Project",
|
|
8052
8331
|
setup_pattern: options.fromDirectory ? "A" : void 0,
|
|
8053
|
-
original_source: options.fromDirectory ?
|
|
8332
|
+
original_source: options.fromDirectory ? resolve7(options.fromDirectory) : void 0
|
|
8054
8333
|
});
|
|
8055
8334
|
if (options.fromDirectory) {
|
|
8056
8335
|
const importer = new SourceImporter(targetDir, sourcesManager);
|
|
8057
|
-
const result = await importer.importDirectory(
|
|
8336
|
+
const result = await importer.importDirectory(resolve7(options.fromDirectory), {
|
|
8058
8337
|
recursive: true
|
|
8059
8338
|
});
|
|
8060
8339
|
sourcesImported = result.imported;
|
|
@@ -8160,16 +8439,16 @@ async function promptMarpInstallChoice() {
|
|
|
8160
8439
|
console.log(` ${chalk7.cyan("2)")} Local install ${chalk7.dim("(creates package.json)")}`);
|
|
8161
8440
|
console.log(` ${chalk7.cyan("3)")} Skip ${chalk7.dim("(I'll install it later)")}`);
|
|
8162
8441
|
console.log("");
|
|
8163
|
-
return new Promise((
|
|
8442
|
+
return new Promise((resolve9) => {
|
|
8164
8443
|
rl.question("Choice [1]: ", (answer) => {
|
|
8165
8444
|
rl.close();
|
|
8166
8445
|
const normalized = answer.trim();
|
|
8167
8446
|
if (normalized === "" || normalized === "1") {
|
|
8168
|
-
|
|
8447
|
+
resolve9("global");
|
|
8169
8448
|
} else if (normalized === "2") {
|
|
8170
|
-
|
|
8449
|
+
resolve9("local");
|
|
8171
8450
|
} else {
|
|
8172
|
-
|
|
8451
|
+
resolve9("skip");
|
|
8173
8452
|
}
|
|
8174
8453
|
});
|
|
8175
8454
|
});
|
|
@@ -8341,10 +8620,8 @@ templates:
|
|
|
8341
8620
|
icons:
|
|
8342
8621
|
# Path to icon registry file
|
|
8343
8622
|
registry: ./icons/registry.yaml
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
directory: .cache/icons
|
|
8347
|
-
ttl: 86400
|
|
8623
|
+
# Path to fetched/pre-bundled icon SVGs
|
|
8624
|
+
fetched: ./icons/fetched
|
|
8348
8625
|
|
|
8349
8626
|
references:
|
|
8350
8627
|
enabled: true
|
|
@@ -8565,14 +8842,20 @@ async function executeWatch(inputPath, options) {
|
|
|
8565
8842
|
}, debounceMs);
|
|
8566
8843
|
};
|
|
8567
8844
|
state.start();
|
|
8845
|
+
const isWSL = !!process.env["WSL_DISTRO_NAME"];
|
|
8568
8846
|
watcher = chokidarWatch2(inputPath, {
|
|
8569
8847
|
persistent: true,
|
|
8570
8848
|
ignoreInitial: true,
|
|
8849
|
+
usePolling: isWSL,
|
|
8850
|
+
...isWSL && { interval: 100 },
|
|
8571
8851
|
awaitWriteFinish: {
|
|
8572
8852
|
stabilityThreshold: 100,
|
|
8573
8853
|
pollInterval: 50
|
|
8574
8854
|
}
|
|
8575
8855
|
});
|
|
8856
|
+
await new Promise((resolve9) => {
|
|
8857
|
+
watcher.on("ready", resolve9);
|
|
8858
|
+
});
|
|
8576
8859
|
watcher.on("change", handleChange);
|
|
8577
8860
|
const cleanup = () => {
|
|
8578
8861
|
state.stop();
|
|
@@ -8591,9 +8874,9 @@ async function executeWatch(inputPath, options) {
|
|
|
8591
8874
|
};
|
|
8592
8875
|
process.on("SIGINT", signalHandler);
|
|
8593
8876
|
process.on("SIGTERM", signalHandler);
|
|
8594
|
-
await new Promise((
|
|
8877
|
+
await new Promise((resolve9) => {
|
|
8595
8878
|
if (options.signal) {
|
|
8596
|
-
options.signal.addEventListener("abort", () =>
|
|
8879
|
+
options.signal.addEventListener("abort", () => resolve9());
|
|
8597
8880
|
}
|
|
8598
8881
|
});
|
|
8599
8882
|
return {
|
|
@@ -9035,7 +9318,7 @@ init_screenshot();
|
|
|
9035
9318
|
init_convert();
|
|
9036
9319
|
import { Command as Command10 } from "commander";
|
|
9037
9320
|
import { access as access13, stat as stat3 } from "fs/promises";
|
|
9038
|
-
import { resolve as
|
|
9321
|
+
import { resolve as resolve8 } from "path";
|
|
9039
9322
|
import chalk10 from "chalk";
|
|
9040
9323
|
import ora5 from "ora";
|
|
9041
9324
|
function createSourcesCommand() {
|
|
@@ -9084,7 +9367,7 @@ function createSourcesSyncCommand() {
|
|
|
9084
9367
|
});
|
|
9085
9368
|
}
|
|
9086
9369
|
async function executeSourcesInit(projectDir, options) {
|
|
9087
|
-
const resolvedDir =
|
|
9370
|
+
const resolvedDir = resolve8(projectDir);
|
|
9088
9371
|
const spinner = ora5("Initializing sources...").start();
|
|
9089
9372
|
try {
|
|
9090
9373
|
const manager = new SourcesManager(resolvedDir);
|
|
@@ -9100,10 +9383,10 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
9100
9383
|
let originalSource;
|
|
9101
9384
|
if (options.fromDirectory) {
|
|
9102
9385
|
setupPattern = "A";
|
|
9103
|
-
originalSource =
|
|
9386
|
+
originalSource = resolve8(options.fromDirectory);
|
|
9104
9387
|
} else if (options.fromFile) {
|
|
9105
9388
|
setupPattern = "B";
|
|
9106
|
-
originalSource =
|
|
9389
|
+
originalSource = resolve8(options.fromFile);
|
|
9107
9390
|
}
|
|
9108
9391
|
await manager.init({
|
|
9109
9392
|
name: projectName,
|
|
@@ -9114,14 +9397,14 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
9114
9397
|
if (options.fromDirectory) {
|
|
9115
9398
|
const importer = new SourceImporter(resolvedDir, manager);
|
|
9116
9399
|
const result = await importer.importDirectory(
|
|
9117
|
-
|
|
9400
|
+
resolve8(options.fromDirectory),
|
|
9118
9401
|
{ recursive: true }
|
|
9119
9402
|
);
|
|
9120
9403
|
filesImported = result.imported;
|
|
9121
9404
|
}
|
|
9122
9405
|
if (options.fromFile) {
|
|
9123
9406
|
const importer = new SourceImporter(resolvedDir, manager);
|
|
9124
|
-
await importer.importFile(
|
|
9407
|
+
await importer.importFile(resolve8(options.fromFile), {
|
|
9125
9408
|
type: "scenario"
|
|
9126
9409
|
});
|
|
9127
9410
|
filesImported = 1;
|
|
@@ -9146,8 +9429,8 @@ async function executeSourcesInit(projectDir, options) {
|
|
|
9146
9429
|
}
|
|
9147
9430
|
}
|
|
9148
9431
|
async function executeSourcesImport(projectDir, sourcePath, options) {
|
|
9149
|
-
const resolvedDir =
|
|
9150
|
-
const resolvedSource =
|
|
9432
|
+
const resolvedDir = resolve8(projectDir);
|
|
9433
|
+
const resolvedSource = resolve8(sourcePath);
|
|
9151
9434
|
const spinner = ora5("Importing files...").start();
|
|
9152
9435
|
try {
|
|
9153
9436
|
const manager = new SourcesManager(resolvedDir);
|
|
@@ -9206,7 +9489,7 @@ async function executeSourcesImport(projectDir, sourcePath, options) {
|
|
|
9206
9489
|
}
|
|
9207
9490
|
}
|
|
9208
9491
|
async function executeSourcesStatus(projectDir, _options) {
|
|
9209
|
-
const resolvedDir =
|
|
9492
|
+
const resolvedDir = resolve8(projectDir);
|
|
9210
9493
|
try {
|
|
9211
9494
|
const manager = new SourcesManager(resolvedDir);
|
|
9212
9495
|
if (!await manager.exists()) {
|
|
@@ -9293,7 +9576,7 @@ async function executeSourcesStatus(projectDir, _options) {
|
|
|
9293
9576
|
}
|
|
9294
9577
|
}
|
|
9295
9578
|
async function executeSourcesSync(projectDir, options) {
|
|
9296
|
-
const resolvedDir =
|
|
9579
|
+
const resolvedDir = resolve8(projectDir);
|
|
9297
9580
|
try {
|
|
9298
9581
|
const manager = new SourcesManager(resolvedDir);
|
|
9299
9582
|
if (!await manager.exists()) {
|