@ncukondo/slide-generation 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +588 -321
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +273 -15
- 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,10 +1275,10 @@ var init_manager = __esm({
|
|
|
998
1275
|
}
|
|
999
1276
|
return map;
|
|
1000
1277
|
}
|
|
1001
|
-
execCommand(cmd) {
|
|
1278
|
+
execCommand(cmd, ignoreExitCode = false) {
|
|
1002
1279
|
return new Promise((resolve8, 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
|
);
|
|
@@ -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();
|
|
@@ -1520,6 +1797,7 @@ var init_pipeline = __esm({
|
|
|
1520
1797
|
this.citationFormatter
|
|
1521
1798
|
);
|
|
1522
1799
|
this.renderer = new Renderer();
|
|
1800
|
+
this.cssCollector = new CSSCollector(this.templateLoader);
|
|
1523
1801
|
}
|
|
1524
1802
|
parser;
|
|
1525
1803
|
templateEngine;
|
|
@@ -1532,6 +1810,7 @@ var init_pipeline = __esm({
|
|
|
1532
1810
|
bibliographyGenerator;
|
|
1533
1811
|
transformer;
|
|
1534
1812
|
renderer;
|
|
1813
|
+
cssCollector;
|
|
1535
1814
|
warnings = [];
|
|
1536
1815
|
/**
|
|
1537
1816
|
* Run the full conversion pipeline
|
|
@@ -1546,7 +1825,7 @@ var init_pipeline = __esm({
|
|
|
1546
1825
|
const transformedSlides = await this.transformSlides(presentation);
|
|
1547
1826
|
const output = this.render(transformedSlides, presentation);
|
|
1548
1827
|
if (options?.outputPath) {
|
|
1549
|
-
await
|
|
1828
|
+
await writeFile2(options.outputPath, output, "utf-8");
|
|
1550
1829
|
}
|
|
1551
1830
|
return output;
|
|
1552
1831
|
} catch (error) {
|
|
@@ -1573,7 +1852,7 @@ var init_pipeline = __esm({
|
|
|
1573
1852
|
const transformedSlides = await this.transformSlides(presentation);
|
|
1574
1853
|
const output = this.render(transformedSlides, presentation);
|
|
1575
1854
|
if (options?.outputPath) {
|
|
1576
|
-
await
|
|
1855
|
+
await writeFile2(options.outputPath, output, "utf-8");
|
|
1577
1856
|
}
|
|
1578
1857
|
return {
|
|
1579
1858
|
output,
|
|
@@ -1687,9 +1966,12 @@ var init_pipeline = __esm({
|
|
|
1687
1966
|
render(slides, presentation) {
|
|
1688
1967
|
try {
|
|
1689
1968
|
const notes = presentation.slides.map((slide) => slide.notes);
|
|
1969
|
+
const templateNames = presentation.slides.map((slide) => slide.template);
|
|
1970
|
+
const templateCss = this.cssCollector.collect(templateNames);
|
|
1690
1971
|
const renderOptions = {
|
|
1691
1972
|
includeTheme: true,
|
|
1692
|
-
notes
|
|
1973
|
+
notes,
|
|
1974
|
+
...templateCss ? { templateCss } : {}
|
|
1693
1975
|
};
|
|
1694
1976
|
return this.renderer.render(slides, presentation.meta, renderOptions);
|
|
1695
1977
|
} catch (error) {
|
|
@@ -1834,9 +2116,9 @@ var init_schema2 = __esm({
|
|
|
1834
2116
|
});
|
|
1835
2117
|
|
|
1836
2118
|
// src/config/loader.ts
|
|
1837
|
-
import { access, readFile as
|
|
1838
|
-
import { join as
|
|
1839
|
-
import { parse as
|
|
2119
|
+
import { access as access2, readFile as readFile6 } from "fs/promises";
|
|
2120
|
+
import { join as join4 } from "path";
|
|
2121
|
+
import { parse as parseYaml4 } from "yaml";
|
|
1840
2122
|
var CONFIG_NAMES, ConfigLoader;
|
|
1841
2123
|
var init_loader2 = __esm({
|
|
1842
2124
|
"src/config/loader.ts"() {
|
|
@@ -1850,9 +2132,9 @@ var init_loader2 = __esm({
|
|
|
1850
2132
|
}
|
|
1851
2133
|
async findConfig(directory) {
|
|
1852
2134
|
for (const name of CONFIG_NAMES) {
|
|
1853
|
-
const path12 =
|
|
2135
|
+
const path12 = join4(directory, name);
|
|
1854
2136
|
try {
|
|
1855
|
-
await
|
|
2137
|
+
await access2(path12);
|
|
1856
2138
|
return path12;
|
|
1857
2139
|
} catch {
|
|
1858
2140
|
}
|
|
@@ -1862,8 +2144,8 @@ var init_loader2 = __esm({
|
|
|
1862
2144
|
async loadFile(configPath) {
|
|
1863
2145
|
if (!configPath) return {};
|
|
1864
2146
|
try {
|
|
1865
|
-
const content = await
|
|
1866
|
-
return
|
|
2147
|
+
const content = await readFile6(configPath, "utf-8");
|
|
2148
|
+
return parseYaml4(content) ?? {};
|
|
1867
2149
|
} catch (error) {
|
|
1868
2150
|
if (error.code === "ENOENT") {
|
|
1869
2151
|
return {};
|
|
@@ -1995,9 +2277,9 @@ var init_schema3 = __esm({
|
|
|
1995
2277
|
});
|
|
1996
2278
|
|
|
1997
2279
|
// src/images/metadata-loader.ts
|
|
1998
|
-
import * as
|
|
1999
|
-
import * as
|
|
2000
|
-
import { parse as
|
|
2280
|
+
import * as fs5 from "fs/promises";
|
|
2281
|
+
import * as path4 from "path";
|
|
2282
|
+
import { parse as parseYaml5 } from "yaml";
|
|
2001
2283
|
var metadataWarnings, ImageMetadataLoader;
|
|
2002
2284
|
var init_metadata_loader = __esm({
|
|
2003
2285
|
"src/images/metadata-loader.ts"() {
|
|
@@ -2026,16 +2308,16 @@ var init_metadata_loader = __esm({
|
|
|
2026
2308
|
*/
|
|
2027
2309
|
async loadDirectory(dirPath) {
|
|
2028
2310
|
const result = /* @__PURE__ */ new Map();
|
|
2029
|
-
const fullDirPath =
|
|
2311
|
+
const fullDirPath = path4.join(this.baseDir, dirPath);
|
|
2030
2312
|
try {
|
|
2031
|
-
await
|
|
2313
|
+
await fs5.access(fullDirPath);
|
|
2032
2314
|
} catch {
|
|
2033
2315
|
return result;
|
|
2034
2316
|
}
|
|
2035
|
-
const files = await
|
|
2317
|
+
const files = await fs5.readdir(fullDirPath);
|
|
2036
2318
|
const imageFiles = files.filter((f) => isImageFile(f));
|
|
2037
2319
|
for (const file of imageFiles) {
|
|
2038
|
-
const imagePath =
|
|
2320
|
+
const imagePath = path4.join(dirPath, file);
|
|
2039
2321
|
const metadata = await this.load(imagePath);
|
|
2040
2322
|
result.set(file, metadata);
|
|
2041
2323
|
}
|
|
@@ -2047,7 +2329,7 @@ var init_metadata_loader = __esm({
|
|
|
2047
2329
|
async hasMetadata(imagePath) {
|
|
2048
2330
|
const individualPath = this.getIndividualMetadataPath(imagePath);
|
|
2049
2331
|
try {
|
|
2050
|
-
await
|
|
2332
|
+
await fs5.access(individualPath);
|
|
2051
2333
|
return true;
|
|
2052
2334
|
} catch {
|
|
2053
2335
|
}
|
|
@@ -2055,7 +2337,7 @@ var init_metadata_loader = __esm({
|
|
|
2055
2337
|
if (dirMetadata === null) {
|
|
2056
2338
|
return false;
|
|
2057
2339
|
}
|
|
2058
|
-
const filename =
|
|
2340
|
+
const filename = path4.basename(imagePath);
|
|
2059
2341
|
return filename in dirMetadata && filename !== "_defaults";
|
|
2060
2342
|
}
|
|
2061
2343
|
/**
|
|
@@ -2069,14 +2351,14 @@ var init_metadata_loader = __esm({
|
|
|
2069
2351
|
* Get the path to individual metadata file
|
|
2070
2352
|
*/
|
|
2071
2353
|
getIndividualMetadataPath(imagePath) {
|
|
2072
|
-
return
|
|
2354
|
+
return path4.join(this.baseDir, `${imagePath}.meta.yaml`);
|
|
2073
2355
|
}
|
|
2074
2356
|
/**
|
|
2075
2357
|
* Get the path to directory metadata file
|
|
2076
2358
|
*/
|
|
2077
2359
|
getDirectoryMetadataPath(imagePath) {
|
|
2078
|
-
const dir =
|
|
2079
|
-
return
|
|
2360
|
+
const dir = path4.dirname(imagePath);
|
|
2361
|
+
return path4.join(this.baseDir, dir, "images.yaml");
|
|
2080
2362
|
}
|
|
2081
2363
|
/**
|
|
2082
2364
|
* Load individual metadata file (.meta.yaml)
|
|
@@ -2084,13 +2366,13 @@ var init_metadata_loader = __esm({
|
|
|
2084
2366
|
async loadIndividualMetadata(imagePath) {
|
|
2085
2367
|
const metadataPath = this.getIndividualMetadataPath(imagePath);
|
|
2086
2368
|
try {
|
|
2087
|
-
await
|
|
2369
|
+
await fs5.access(metadataPath);
|
|
2088
2370
|
} catch {
|
|
2089
2371
|
return null;
|
|
2090
2372
|
}
|
|
2091
2373
|
try {
|
|
2092
|
-
const content = await
|
|
2093
|
-
const parsed =
|
|
2374
|
+
const content = await fs5.readFile(metadataPath, "utf-8");
|
|
2375
|
+
const parsed = parseYaml5(content);
|
|
2094
2376
|
const validated = individualMetadataSchema.safeParse(parsed);
|
|
2095
2377
|
if (validated.success) {
|
|
2096
2378
|
return validated.data;
|
|
@@ -2115,13 +2397,13 @@ var init_metadata_loader = __esm({
|
|
|
2115
2397
|
async loadDirectoryMetadataFile(imagePath) {
|
|
2116
2398
|
const metadataPath = this.getDirectoryMetadataPath(imagePath);
|
|
2117
2399
|
try {
|
|
2118
|
-
await
|
|
2400
|
+
await fs5.access(metadataPath);
|
|
2119
2401
|
} catch {
|
|
2120
2402
|
return null;
|
|
2121
2403
|
}
|
|
2122
2404
|
try {
|
|
2123
|
-
const content = await
|
|
2124
|
-
const parsed =
|
|
2405
|
+
const content = await fs5.readFile(metadataPath, "utf-8");
|
|
2406
|
+
const parsed = parseYaml5(content);
|
|
2125
2407
|
const validated = directoryMetadataSchema.safeParse(parsed);
|
|
2126
2408
|
if (validated.success) {
|
|
2127
2409
|
return validated.data;
|
|
@@ -2148,7 +2430,7 @@ var init_metadata_loader = __esm({
|
|
|
2148
2430
|
if (dirMetadata === null) {
|
|
2149
2431
|
return {};
|
|
2150
2432
|
}
|
|
2151
|
-
const filename =
|
|
2433
|
+
const filename = path4.basename(imagePath);
|
|
2152
2434
|
const defaults = dirMetadata["_defaults"] ?? {};
|
|
2153
2435
|
const fileMetadata = dirMetadata[filename] ?? {};
|
|
2154
2436
|
return this.mergeMetadata(defaults, fileMetadata);
|
|
@@ -2183,8 +2465,8 @@ var init_metadata_loader = __esm({
|
|
|
2183
2465
|
});
|
|
2184
2466
|
|
|
2185
2467
|
// src/images/validator.ts
|
|
2186
|
-
import * as
|
|
2187
|
-
import * as
|
|
2468
|
+
import * as fs6 from "fs/promises";
|
|
2469
|
+
import * as path5 from "path";
|
|
2188
2470
|
import imageSize from "image-size";
|
|
2189
2471
|
var ImageValidator;
|
|
2190
2472
|
var init_validator = __esm({
|
|
@@ -2204,14 +2486,14 @@ var init_validator = __esm({
|
|
|
2204
2486
|
async validateImageExists(imagePath) {
|
|
2205
2487
|
const errors = [];
|
|
2206
2488
|
const warnings = [];
|
|
2207
|
-
const fullPath =
|
|
2208
|
-
const ext =
|
|
2489
|
+
const fullPath = path5.join(this.baseDir, imagePath);
|
|
2490
|
+
const ext = path5.extname(imagePath).toLowerCase();
|
|
2209
2491
|
if (!IMAGE_EXTENSIONS.has(ext)) {
|
|
2210
2492
|
errors.push(`Unsupported image format: ${imagePath}`);
|
|
2211
2493
|
return { valid: false, errors, warnings };
|
|
2212
2494
|
}
|
|
2213
2495
|
try {
|
|
2214
|
-
await
|
|
2496
|
+
await fs6.access(fullPath);
|
|
2215
2497
|
} catch {
|
|
2216
2498
|
errors.push(`Image not found: ${imagePath}`);
|
|
2217
2499
|
return { valid: false, errors, warnings };
|
|
@@ -2222,9 +2504,9 @@ var init_validator = __esm({
|
|
|
2222
2504
|
* Get image dimensions
|
|
2223
2505
|
*/
|
|
2224
2506
|
async getImageDimensions(imagePath) {
|
|
2225
|
-
const fullPath =
|
|
2507
|
+
const fullPath = path5.join(this.baseDir, imagePath);
|
|
2226
2508
|
try {
|
|
2227
|
-
const buffer = await
|
|
2509
|
+
const buffer = await fs6.readFile(fullPath);
|
|
2228
2510
|
const dimensions = imageSize(buffer);
|
|
2229
2511
|
if (dimensions.width && dimensions.height) {
|
|
2230
2512
|
return {
|
|
@@ -2559,8 +2841,8 @@ var init_processor = __esm({
|
|
|
2559
2841
|
});
|
|
2560
2842
|
|
|
2561
2843
|
// src/images/processing-pipeline.ts
|
|
2562
|
-
import * as
|
|
2563
|
-
import * as
|
|
2844
|
+
import * as fs7 from "fs/promises";
|
|
2845
|
+
import * as path6 from "path";
|
|
2564
2846
|
var ImageProcessingPipeline;
|
|
2565
2847
|
var init_processing_pipeline = __esm({
|
|
2566
2848
|
"src/images/processing-pipeline.ts"() {
|
|
@@ -2573,7 +2855,7 @@ var init_processing_pipeline = __esm({
|
|
|
2573
2855
|
this.baseDir = baseDir;
|
|
2574
2856
|
this.metadataLoader = new ImageMetadataLoader(baseDir);
|
|
2575
2857
|
this.processor = new ImageProcessor();
|
|
2576
|
-
this.outputDir = options?.outputDir ??
|
|
2858
|
+
this.outputDir = options?.outputDir ?? path6.join(baseDir, ".processed");
|
|
2577
2859
|
}
|
|
2578
2860
|
metadataLoader;
|
|
2579
2861
|
processor;
|
|
@@ -2582,7 +2864,7 @@ var init_processing_pipeline = __esm({
|
|
|
2582
2864
|
* Process a single image based on its metadata instructions
|
|
2583
2865
|
*/
|
|
2584
2866
|
async processImage(imagePath) {
|
|
2585
|
-
const fullPath =
|
|
2867
|
+
const fullPath = path6.join(this.baseDir, imagePath);
|
|
2586
2868
|
const result = {
|
|
2587
2869
|
success: true,
|
|
2588
2870
|
originalPath: fullPath
|
|
@@ -2593,7 +2875,7 @@ var init_processing_pipeline = __esm({
|
|
|
2593
2875
|
result.skipped = true;
|
|
2594
2876
|
return result;
|
|
2595
2877
|
}
|
|
2596
|
-
await
|
|
2878
|
+
await fs7.mkdir(this.outputDir, { recursive: true });
|
|
2597
2879
|
const processedPath = await this.applyInstructions(
|
|
2598
2880
|
fullPath,
|
|
2599
2881
|
imagePath,
|
|
@@ -2622,7 +2904,7 @@ var init_processing_pipeline = __esm({
|
|
|
2622
2904
|
imageMap: /* @__PURE__ */ new Map()
|
|
2623
2905
|
};
|
|
2624
2906
|
try {
|
|
2625
|
-
const files = await
|
|
2907
|
+
const files = await fs7.readdir(this.baseDir);
|
|
2626
2908
|
const imageFiles = files.filter((f) => isImageFile(f));
|
|
2627
2909
|
result.totalImages = imageFiles.length;
|
|
2628
2910
|
for (const imageFile of imageFiles) {
|
|
@@ -2646,11 +2928,11 @@ var init_processing_pipeline = __esm({
|
|
|
2646
2928
|
* Apply processing instructions to an image
|
|
2647
2929
|
*/
|
|
2648
2930
|
async applyInstructions(inputPath, relativePath, instructions) {
|
|
2649
|
-
const outputFilename =
|
|
2650
|
-
const finalOutputPath =
|
|
2931
|
+
const outputFilename = path6.basename(relativePath);
|
|
2932
|
+
const finalOutputPath = path6.join(this.outputDir, outputFilename);
|
|
2651
2933
|
const tempPaths = [
|
|
2652
|
-
|
|
2653
|
-
|
|
2934
|
+
path6.join(this.outputDir, `temp_${outputFilename}`),
|
|
2935
|
+
path6.join(this.outputDir, `temp2_${outputFilename}`)
|
|
2654
2936
|
];
|
|
2655
2937
|
let currentInputPath = inputPath;
|
|
2656
2938
|
for (let i = 0; i < instructions.length; i++) {
|
|
@@ -2663,11 +2945,11 @@ var init_processing_pipeline = __esm({
|
|
|
2663
2945
|
}
|
|
2664
2946
|
}
|
|
2665
2947
|
try {
|
|
2666
|
-
await
|
|
2948
|
+
await fs7.unlink(path6.join(this.outputDir, `temp_${outputFilename}`));
|
|
2667
2949
|
} catch {
|
|
2668
2950
|
}
|
|
2669
2951
|
try {
|
|
2670
|
-
await
|
|
2952
|
+
await fs7.unlink(path6.join(this.outputDir, `temp2_${outputFilename}`));
|
|
2671
2953
|
} catch {
|
|
2672
2954
|
}
|
|
2673
2955
|
return finalOutputPath;
|
|
@@ -2753,19 +3035,19 @@ var init_images = __esm({
|
|
|
2753
3035
|
|
|
2754
3036
|
// src/cli/commands/convert.ts
|
|
2755
3037
|
import { Command } from "commander";
|
|
2756
|
-
import { access as
|
|
2757
|
-
import { basename as basename3, dirname as dirname2, join as
|
|
3038
|
+
import { access as access5, readFile as readFile9 } from "fs/promises";
|
|
3039
|
+
import { basename as basename3, dirname as dirname2, join as join8 } from "path";
|
|
2758
3040
|
import chalk from "chalk";
|
|
2759
3041
|
import ora from "ora";
|
|
2760
|
-
import { parse as
|
|
3042
|
+
import { parse as parseYaml6 } from "yaml";
|
|
2761
3043
|
function getDefaultOutputPath(inputPath) {
|
|
2762
3044
|
const dir = dirname2(inputPath);
|
|
2763
3045
|
const base = basename3(inputPath, ".yaml");
|
|
2764
|
-
return
|
|
3046
|
+
return join8(dir, `${base}.md`);
|
|
2765
3047
|
}
|
|
2766
3048
|
async function extractImageDirectories(inputPath, baseDir) {
|
|
2767
|
-
const content = await
|
|
2768
|
-
const parsed =
|
|
3049
|
+
const content = await readFile9(inputPath, "utf-8");
|
|
3050
|
+
const parsed = parseYaml6(content);
|
|
2769
3051
|
const imageDirs = /* @__PURE__ */ new Set();
|
|
2770
3052
|
if (!parsed.slides) return [];
|
|
2771
3053
|
for (const slide of parsed.slides) {
|
|
@@ -2788,7 +3070,7 @@ async function extractImageDirectories(inputPath, baseDir) {
|
|
|
2788
3070
|
for (const imagePath of imagePaths) {
|
|
2789
3071
|
const dir = dirname2(imagePath);
|
|
2790
3072
|
if (dir && dir !== ".") {
|
|
2791
|
-
imageDirs.add(
|
|
3073
|
+
imageDirs.add(join8(baseDir, dir));
|
|
2792
3074
|
}
|
|
2793
3075
|
}
|
|
2794
3076
|
}
|
|
@@ -2799,7 +3081,7 @@ async function processImages(inputPath, baseDir) {
|
|
|
2799
3081
|
let totalProcessed = 0;
|
|
2800
3082
|
for (const imageDir of imageDirs) {
|
|
2801
3083
|
try {
|
|
2802
|
-
await
|
|
3084
|
+
await access5(imageDir);
|
|
2803
3085
|
const pipeline = new ImageProcessingPipeline(imageDir);
|
|
2804
3086
|
const result = await pipeline.processDirectory();
|
|
2805
3087
|
totalProcessed += result.processedImages;
|
|
@@ -2824,7 +3106,7 @@ async function executeConvert(inputPath, options) {
|
|
|
2824
3106
|
try {
|
|
2825
3107
|
spinner?.start(`Reading ${inputPath}...`);
|
|
2826
3108
|
try {
|
|
2827
|
-
await
|
|
3109
|
+
await access5(inputPath);
|
|
2828
3110
|
} catch {
|
|
2829
3111
|
spinner?.fail(`File not found: ${inputPath}`);
|
|
2830
3112
|
console.error(chalk.red(`Error: Input file not found: ${inputPath}`));
|
|
@@ -2942,7 +3224,7 @@ var init_convert = __esm({
|
|
|
2942
3224
|
|
|
2943
3225
|
// src/cli/utils/marp-runner.ts
|
|
2944
3226
|
import { existsSync } from "fs";
|
|
2945
|
-
import { join as
|
|
3227
|
+
import { join as join9, resolve, dirname as dirname4 } from "path";
|
|
2946
3228
|
import {
|
|
2947
3229
|
execFileSync,
|
|
2948
3230
|
spawn
|
|
@@ -2951,7 +3233,7 @@ function getMarpCommand(projectDir) {
|
|
|
2951
3233
|
const startDir = resolve(projectDir ?? process.cwd());
|
|
2952
3234
|
let currentDir = startDir;
|
|
2953
3235
|
while (true) {
|
|
2954
|
-
const localMarp =
|
|
3236
|
+
const localMarp = join9(currentDir, "node_modules", ".bin", "marp");
|
|
2955
3237
|
if (existsSync(localMarp)) {
|
|
2956
3238
|
return localMarp;
|
|
2957
3239
|
}
|
|
@@ -3013,13 +3295,20 @@ function parseMarpBrowserError(errorOutput) {
|
|
|
3013
3295
|
}
|
|
3014
3296
|
return null;
|
|
3015
3297
|
}
|
|
3298
|
+
function addHtmlOption(args) {
|
|
3299
|
+
if (args.includes("--html") || args.includes("--no-html")) {
|
|
3300
|
+
return args;
|
|
3301
|
+
}
|
|
3302
|
+
return ["--html", ...args];
|
|
3303
|
+
}
|
|
3016
3304
|
function runMarp(args, options = {}) {
|
|
3017
3305
|
const { projectDir, ...execOptions } = options;
|
|
3018
3306
|
const marpCmd = getMarpCommand(projectDir);
|
|
3019
3307
|
if (!marpCmd) {
|
|
3020
3308
|
throw new Error("Marp CLI not found. Install it with: npm install -D @marp-team/marp-cli");
|
|
3021
3309
|
}
|
|
3022
|
-
|
|
3310
|
+
const finalArgs = addHtmlOption(args);
|
|
3311
|
+
execFileSync(marpCmd, finalArgs, execOptions);
|
|
3023
3312
|
}
|
|
3024
3313
|
function spawnMarp(args, options = {}) {
|
|
3025
3314
|
const { projectDir, ...spawnOptions } = options;
|
|
@@ -3027,7 +3316,8 @@ function spawnMarp(args, options = {}) {
|
|
|
3027
3316
|
if (!marpCmd) {
|
|
3028
3317
|
throw new Error("Marp CLI not found. Install it with: npm install -D @marp-team/marp-cli");
|
|
3029
3318
|
}
|
|
3030
|
-
|
|
3319
|
+
const finalArgs = addHtmlOption(args);
|
|
3320
|
+
return spawn(marpCmd, finalArgs, spawnOptions);
|
|
3031
3321
|
}
|
|
3032
3322
|
var init_marp_runner = __esm({
|
|
3033
3323
|
"src/cli/utils/marp-runner.ts"() {
|
|
@@ -3051,17 +3341,17 @@ __export(screenshot_exports, {
|
|
|
3051
3341
|
generateContactSheet: () => generateContactSheet
|
|
3052
3342
|
});
|
|
3053
3343
|
import { Command as Command4 } from "commander";
|
|
3054
|
-
import { access as
|
|
3055
|
-
import { basename as basename5, dirname as dirname6, extname as extname3, join as
|
|
3344
|
+
import { access as access8, mkdir as mkdir4, readdir as readdir5, rename, unlink as unlink2 } from "fs/promises";
|
|
3345
|
+
import { basename as basename5, dirname as dirname6, extname as extname3, join as join11 } from "path";
|
|
3056
3346
|
import chalk4 from "chalk";
|
|
3057
3347
|
import ora3 from "ora";
|
|
3058
3348
|
import sharp2 from "sharp";
|
|
3059
3349
|
async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
3060
3350
|
const slideStr = slideNumber.toString().padStart(3, "0");
|
|
3061
3351
|
const targetFileName = `${baseName}.${slideStr}.${format}`;
|
|
3062
|
-
const targetPath =
|
|
3352
|
+
const targetPath = join11(outputDir, targetFileName);
|
|
3063
3353
|
try {
|
|
3064
|
-
await
|
|
3354
|
+
await access8(targetPath);
|
|
3065
3355
|
} catch {
|
|
3066
3356
|
return {
|
|
3067
3357
|
success: false,
|
|
@@ -3074,7 +3364,7 @@ async function filterToSpecificSlide(outputDir, baseName, slideNumber, format) {
|
|
|
3074
3364
|
);
|
|
3075
3365
|
for (const file of slideFiles) {
|
|
3076
3366
|
if (file !== targetFileName) {
|
|
3077
|
-
await unlink2(
|
|
3367
|
+
await unlink2(join11(outputDir, file));
|
|
3078
3368
|
}
|
|
3079
3369
|
}
|
|
3080
3370
|
return {
|
|
@@ -3091,8 +3381,8 @@ async function ensureFileExtensions(outputDir, baseName, format) {
|
|
|
3091
3381
|
const renamedFiles = [];
|
|
3092
3382
|
for (const file of files) {
|
|
3093
3383
|
if (file.startsWith(baseName) && slidePattern.test(file)) {
|
|
3094
|
-
const oldPath =
|
|
3095
|
-
const newPath =
|
|
3384
|
+
const oldPath = join11(outputDir, file);
|
|
3385
|
+
const newPath = join11(outputDir, `${file}.${format}`);
|
|
3096
3386
|
await rename(oldPath, newPath);
|
|
3097
3387
|
renamedFiles.push(`${file}.${format}`);
|
|
3098
3388
|
}
|
|
@@ -3186,14 +3476,14 @@ function formatAiOutput(options) {
|
|
|
3186
3476
|
""
|
|
3187
3477
|
];
|
|
3188
3478
|
for (const file of files) {
|
|
3189
|
-
lines.push(` ${
|
|
3479
|
+
lines.push(` ${join11(outputDir, file)}`);
|
|
3190
3480
|
}
|
|
3191
3481
|
lines.push("");
|
|
3192
3482
|
lines.push(`Estimated tokens: ~${totalTokens} (${files.length} ${imageLabel})`);
|
|
3193
3483
|
if (files.length > 0) {
|
|
3194
3484
|
lines.push("");
|
|
3195
3485
|
lines.push("To review in Claude Code:");
|
|
3196
|
-
lines.push(` Read ${
|
|
3486
|
+
lines.push(` Read ${join11(outputDir, files[0])}`);
|
|
3197
3487
|
}
|
|
3198
3488
|
return lines.join("\n");
|
|
3199
3489
|
}
|
|
@@ -3211,7 +3501,7 @@ function buildMarpCommandArgs(markdownPath, outputDir, options) {
|
|
|
3211
3501
|
args.push("--jpeg-quality", String(quality));
|
|
3212
3502
|
}
|
|
3213
3503
|
const mdBaseName = basename5(markdownPath, extname3(markdownPath));
|
|
3214
|
-
const outputPath =
|
|
3504
|
+
const outputPath = join11(outputDir, mdBaseName);
|
|
3215
3505
|
args.push("-o", outputPath);
|
|
3216
3506
|
args.push(markdownPath);
|
|
3217
3507
|
return args;
|
|
@@ -3226,7 +3516,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
3226
3516
|
const spinner = options.verbose ? null : ora3();
|
|
3227
3517
|
const outputDir = options.output || "./screenshots";
|
|
3228
3518
|
try {
|
|
3229
|
-
await
|
|
3519
|
+
await access8(inputPath);
|
|
3230
3520
|
} catch {
|
|
3231
3521
|
const message = `File not found: ${inputPath}`;
|
|
3232
3522
|
console.error(chalk4.red(`Error: ${message}`));
|
|
@@ -3252,7 +3542,7 @@ async function executeScreenshot(inputPath, options) {
|
|
|
3252
3542
|
}
|
|
3253
3543
|
spinner?.succeed("Marp CLI found");
|
|
3254
3544
|
try {
|
|
3255
|
-
await
|
|
3545
|
+
await mkdir4(outputDir, { recursive: true });
|
|
3256
3546
|
} catch {
|
|
3257
3547
|
const message = `Failed to create output directory: ${outputDir}`;
|
|
3258
3548
|
console.error(chalk4.red(`Error: ${message}`));
|
|
@@ -3359,7 +3649,7 @@ Error: ${browserError.message}
|
|
|
3359
3649
|
const generatedFiles = allFiles.filter((f) => f.startsWith(mdBaseName) && f.endsWith(`.${actualFormat}`)).sort();
|
|
3360
3650
|
if (generatedFiles.length > 0) {
|
|
3361
3651
|
try {
|
|
3362
|
-
const metadata = await sharp2(
|
|
3652
|
+
const metadata = await sharp2(join11(outputDir, generatedFiles[0])).metadata();
|
|
3363
3653
|
if (metadata.width && metadata.height) {
|
|
3364
3654
|
actualWidth = metadata.width;
|
|
3365
3655
|
actualHeight = metadata.height;
|
|
@@ -3370,10 +3660,10 @@ Error: ${browserError.message}
|
|
|
3370
3660
|
if (options.contactSheet && generatedFiles.length > 0) {
|
|
3371
3661
|
spinner?.start("Generating contact sheet...");
|
|
3372
3662
|
const slides = generatedFiles.map((file, index) => ({
|
|
3373
|
-
path:
|
|
3663
|
+
path: join11(outputDir, file),
|
|
3374
3664
|
index: index + 1
|
|
3375
3665
|
}));
|
|
3376
|
-
const contactSheetPath =
|
|
3666
|
+
const contactSheetPath = join11(outputDir, `${mdBaseName}-contact.png`);
|
|
3377
3667
|
const contactResult = await generateContactSheet(slides, {
|
|
3378
3668
|
outputPath: contactSheetPath,
|
|
3379
3669
|
columns: options.columns || 2,
|
|
@@ -3433,13 +3723,9 @@ init_transformer();
|
|
|
3433
3723
|
init_renderer();
|
|
3434
3724
|
init_pipeline();
|
|
3435
3725
|
|
|
3436
|
-
// src/templates/index.ts
|
|
3437
|
-
init_engine();
|
|
3438
|
-
init_loader();
|
|
3439
|
-
init_validators();
|
|
3440
|
-
|
|
3441
3726
|
// src/index.ts
|
|
3442
|
-
|
|
3727
|
+
init_templates();
|
|
3728
|
+
var VERSION = "0.8.0";
|
|
3443
3729
|
|
|
3444
3730
|
// src/cli/index.ts
|
|
3445
3731
|
init_convert();
|
|
@@ -3451,11 +3737,11 @@ init_registry();
|
|
|
3451
3737
|
init_loader2();
|
|
3452
3738
|
init_convert();
|
|
3453
3739
|
import { Command as Command2 } from "commander";
|
|
3454
|
-
import { access as
|
|
3740
|
+
import { access as access6, readFile as readFile10 } from "fs/promises";
|
|
3455
3741
|
import { dirname as dirname3 } from "path";
|
|
3456
3742
|
import chalk2 from "chalk";
|
|
3457
3743
|
import ora2 from "ora";
|
|
3458
|
-
import { parse as
|
|
3744
|
+
import { parse as parseYaml7, stringify as yamlStringify } from "yaml";
|
|
3459
3745
|
|
|
3460
3746
|
// src/references/index.ts
|
|
3461
3747
|
init_extractor();
|
|
@@ -3696,7 +3982,7 @@ async function executeValidate(inputPath, options) {
|
|
|
3696
3982
|
try {
|
|
3697
3983
|
spinner?.start(`Validating ${inputPath}...`);
|
|
3698
3984
|
try {
|
|
3699
|
-
await
|
|
3985
|
+
await access6(inputPath);
|
|
3700
3986
|
} catch {
|
|
3701
3987
|
spinner?.fail(`File not found: ${inputPath}`);
|
|
3702
3988
|
result.errors.push(`File not found: ${inputPath}`);
|
|
@@ -3705,9 +3991,9 @@ async function executeValidate(inputPath, options) {
|
|
|
3705
3991
|
process.exitCode = ExitCode.FileReadError;
|
|
3706
3992
|
return;
|
|
3707
3993
|
}
|
|
3708
|
-
const content = await
|
|
3994
|
+
const content = await readFile10(inputPath, "utf-8");
|
|
3709
3995
|
try {
|
|
3710
|
-
|
|
3996
|
+
parseYaml7(content);
|
|
3711
3997
|
result.stats.yamlSyntax = true;
|
|
3712
3998
|
} catch (error) {
|
|
3713
3999
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -4028,15 +4314,16 @@ function outputResult(result, options) {
|
|
|
4028
4314
|
}
|
|
4029
4315
|
|
|
4030
4316
|
// src/cli/commands/templates.ts
|
|
4317
|
+
init_templates();
|
|
4318
|
+
init_loader2();
|
|
4319
|
+
init_pipeline();
|
|
4320
|
+
init_convert();
|
|
4031
4321
|
import { Command as Command5 } from "commander";
|
|
4032
4322
|
import chalk5 from "chalk";
|
|
4033
4323
|
import * as yaml2 from "yaml";
|
|
4034
|
-
import { mkdir as
|
|
4035
|
-
import { join as
|
|
4324
|
+
import { mkdir as mkdir5, writeFile as writeFile4, rm as rm2, readdir as readdir6 } from "fs/promises";
|
|
4325
|
+
import { join as join12, basename as basename6 } from "path";
|
|
4036
4326
|
import { tmpdir as tmpdir2 } from "os";
|
|
4037
|
-
init_loader2();
|
|
4038
|
-
init_pipeline();
|
|
4039
|
-
init_convert();
|
|
4040
4327
|
|
|
4041
4328
|
// src/cli/commands/preview.ts
|
|
4042
4329
|
init_pipeline();
|
|
@@ -4044,9 +4331,9 @@ init_loader2();
|
|
|
4044
4331
|
init_convert();
|
|
4045
4332
|
init_marp_runner();
|
|
4046
4333
|
import { Command as Command3 } from "commander";
|
|
4047
|
-
import { access as
|
|
4048
|
-
import { basename as basename4, dirname as dirname5, join as
|
|
4049
|
-
import * as
|
|
4334
|
+
import { access as access7, readdir as readdir4, mkdir as mkdir3, writeFile as writeFile3, readFile as readFile11, rm } from "fs/promises";
|
|
4335
|
+
import { basename as basename4, dirname as dirname5, join as join10, extname as extname2 } from "path";
|
|
4336
|
+
import * as path7 from "path";
|
|
4050
4337
|
import { tmpdir } from "os";
|
|
4051
4338
|
import { createServer } from "http";
|
|
4052
4339
|
import chalk3 from "chalk";
|
|
@@ -4164,7 +4451,7 @@ async function collectSlideInfo(dir, baseName, format) {
|
|
|
4164
4451
|
if (match) {
|
|
4165
4452
|
const index = parseInt(match[1], 10);
|
|
4166
4453
|
slides.push({
|
|
4167
|
-
path:
|
|
4454
|
+
path: join10(dir, file),
|
|
4168
4455
|
title: `Slide ${index}`,
|
|
4169
4456
|
index
|
|
4170
4457
|
});
|
|
@@ -4180,7 +4467,7 @@ async function checkMarpCliAvailable(projectDir) {
|
|
|
4180
4467
|
}
|
|
4181
4468
|
function getTempPreviewDir(inputPath) {
|
|
4182
4469
|
const base = basename4(inputPath, ".yaml");
|
|
4183
|
-
return
|
|
4470
|
+
return join10(tmpdir(), `slide-gen-preview-${base}-${Date.now()}`);
|
|
4184
4471
|
}
|
|
4185
4472
|
function buildMarpCommand(markdownDir, options) {
|
|
4186
4473
|
const parts = ["marp", "--server", "-I", markdownDir];
|
|
@@ -4203,8 +4490,8 @@ function startStaticServer(baseDir, port, options = {}) {
|
|
|
4203
4490
|
try {
|
|
4204
4491
|
const urlPath = new URL(req.url || "/", `http://localhost`).pathname;
|
|
4205
4492
|
const requestedPath = urlPath === "/" ? "/index.html" : urlPath;
|
|
4206
|
-
const resolvedBaseDir =
|
|
4207
|
-
const filePath =
|
|
4493
|
+
const resolvedBaseDir = path7.resolve(baseDir);
|
|
4494
|
+
const filePath = path7.resolve(baseDir, "." + requestedPath);
|
|
4208
4495
|
if (!filePath.startsWith(resolvedBaseDir + "/") && filePath !== resolvedBaseDir) {
|
|
4209
4496
|
res.writeHead(403);
|
|
4210
4497
|
res.end("Forbidden");
|
|
@@ -4212,7 +4499,7 @@ function startStaticServer(baseDir, port, options = {}) {
|
|
|
4212
4499
|
}
|
|
4213
4500
|
const ext = extname2(filePath);
|
|
4214
4501
|
const contentType = mimeTypes[ext] || "application/octet-stream";
|
|
4215
|
-
const data = await
|
|
4502
|
+
const data = await readFile11(filePath);
|
|
4216
4503
|
res.writeHead(200, { "Content-Type": contentType });
|
|
4217
4504
|
res.end(data);
|
|
4218
4505
|
} catch {
|
|
@@ -4234,9 +4521,9 @@ function startStaticServer(baseDir, port, options = {}) {
|
|
|
4234
4521
|
async function executeGalleryPreview(inputPath, options) {
|
|
4235
4522
|
const errors = [];
|
|
4236
4523
|
const port = Number(options.port) || 8080;
|
|
4237
|
-
const galleryDir =
|
|
4524
|
+
const galleryDir = join10(tmpdir(), `slide-gen-gallery-${Date.now()}`);
|
|
4238
4525
|
try {
|
|
4239
|
-
await
|
|
4526
|
+
await access7(inputPath);
|
|
4240
4527
|
} catch {
|
|
4241
4528
|
console.error(chalk3.red(`Error: File not found: ${inputPath}`));
|
|
4242
4529
|
errors.push(`File not found: ${inputPath}`);
|
|
@@ -4257,7 +4544,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
4257
4544
|
return { success: false, errors };
|
|
4258
4545
|
}
|
|
4259
4546
|
console.log(chalk3.green("\u2713") + " Marp CLI found");
|
|
4260
|
-
await
|
|
4547
|
+
await mkdir3(galleryDir, { recursive: true });
|
|
4261
4548
|
const configLoader = new ConfigLoader();
|
|
4262
4549
|
let configPath = options.config;
|
|
4263
4550
|
if (!configPath) {
|
|
@@ -4276,7 +4563,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
4276
4563
|
await rm(galleryDir, { recursive: true, force: true });
|
|
4277
4564
|
return { success: false, errors };
|
|
4278
4565
|
}
|
|
4279
|
-
const tempMdPath =
|
|
4566
|
+
const tempMdPath = join10(galleryDir, "slides.md");
|
|
4280
4567
|
console.log(`Converting ${chalk3.cyan(inputPath)}...`);
|
|
4281
4568
|
try {
|
|
4282
4569
|
await pipeline.runWithResult(inputPath, { outputPath: tempMdPath });
|
|
@@ -4317,7 +4604,7 @@ async function executeGalleryPreview(inputPath, options) {
|
|
|
4317
4604
|
path: basename4(s.path)
|
|
4318
4605
|
}));
|
|
4319
4606
|
const galleryHtml = generateGalleryHtml(relativeSlides);
|
|
4320
|
-
await
|
|
4607
|
+
await writeFile3(join10(galleryDir, "index.html"), galleryHtml);
|
|
4321
4608
|
console.log(`
|
|
4322
4609
|
Starting gallery server on port ${chalk3.cyan(port)}...`);
|
|
4323
4610
|
let server;
|
|
@@ -4381,7 +4668,7 @@ async function executePreview(inputPath, options) {
|
|
|
4381
4668
|
const port = Number(options.port) || 8080;
|
|
4382
4669
|
if (options.signal?.aborted) {
|
|
4383
4670
|
try {
|
|
4384
|
-
await
|
|
4671
|
+
await access7(inputPath);
|
|
4385
4672
|
} catch {
|
|
4386
4673
|
errors.push(`File not found: ${inputPath}`);
|
|
4387
4674
|
return { success: false, errors };
|
|
@@ -4389,7 +4676,7 @@ async function executePreview(inputPath, options) {
|
|
|
4389
4676
|
return { success: true, errors };
|
|
4390
4677
|
}
|
|
4391
4678
|
try {
|
|
4392
|
-
await
|
|
4679
|
+
await access7(inputPath);
|
|
4393
4680
|
} catch {
|
|
4394
4681
|
console.error(chalk3.red(`Error: File not found: ${inputPath}`));
|
|
4395
4682
|
errors.push(`File not found: ${inputPath}`);
|
|
@@ -4427,8 +4714,8 @@ async function executePreview(inputPath, options) {
|
|
|
4427
4714
|
return { success: false, errors };
|
|
4428
4715
|
}
|
|
4429
4716
|
const tempDir = getTempPreviewDir(inputPath);
|
|
4430
|
-
await
|
|
4431
|
-
const tempMarkdownPath =
|
|
4717
|
+
await mkdir3(tempDir, { recursive: true });
|
|
4718
|
+
const tempMarkdownPath = join10(tempDir, "slides.md");
|
|
4432
4719
|
console.log(`Converting ${chalk3.cyan(inputPath)}...`);
|
|
4433
4720
|
try {
|
|
4434
4721
|
await pipeline.runWithResult(inputPath, { outputPath: tempMarkdownPath });
|
|
@@ -4944,7 +5231,7 @@ async function executeTemplatePreview(name, options) {
|
|
|
4944
5231
|
return;
|
|
4945
5232
|
}
|
|
4946
5233
|
const port = Number(options.port) || 8080;
|
|
4947
|
-
const previewDir =
|
|
5234
|
+
const previewDir = join12(tmpdir2(), `slide-gen-template-preview-${Date.now()}`);
|
|
4948
5235
|
console.log("Checking for Marp CLI...");
|
|
4949
5236
|
const marpAvailable = await checkMarpCliAvailable();
|
|
4950
5237
|
if (!marpAvailable) {
|
|
@@ -4981,7 +5268,7 @@ async function executeTemplatePreview(name, options) {
|
|
|
4981
5268
|
return;
|
|
4982
5269
|
}
|
|
4983
5270
|
console.log(`Found ${templates.length} template(s) to preview`);
|
|
4984
|
-
await
|
|
5271
|
+
await mkdir5(previewDir, { recursive: true });
|
|
4985
5272
|
console.log("Initializing pipeline...");
|
|
4986
5273
|
const pipeline = new Pipeline(config);
|
|
4987
5274
|
try {
|
|
@@ -4997,9 +5284,9 @@ async function executeTemplatePreview(name, options) {
|
|
|
4997
5284
|
for (const template of templates) {
|
|
4998
5285
|
console.log(`Processing template: ${chalk5.cyan(template.name)}...`);
|
|
4999
5286
|
const sampleYaml = generateSampleYaml(template);
|
|
5000
|
-
const yamlPath =
|
|
5001
|
-
await
|
|
5002
|
-
const mdPath =
|
|
5287
|
+
const yamlPath = join12(previewDir, `${template.name}.yaml`);
|
|
5288
|
+
await writeFile4(yamlPath, sampleYaml);
|
|
5289
|
+
const mdPath = join12(previewDir, `${template.name}.md`);
|
|
5003
5290
|
try {
|
|
5004
5291
|
await pipeline.runWithResult(yamlPath, { outputPath: mdPath });
|
|
5005
5292
|
} catch (error) {
|
|
@@ -5032,7 +5319,7 @@ async function executeTemplatePreview(name, options) {
|
|
|
5032
5319
|
return;
|
|
5033
5320
|
}
|
|
5034
5321
|
const previewHtml = generateTemplatePreviewHtml(templatePreviews);
|
|
5035
|
-
await
|
|
5322
|
+
await writeFile4(join12(previewDir, "index.html"), previewHtml);
|
|
5036
5323
|
console.log(`
|
|
5037
5324
|
Starting preview server on port ${chalk5.cyan(port)}...`);
|
|
5038
5325
|
let server;
|
|
@@ -5114,7 +5401,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5114
5401
|
return { success: true, errors: [], files: [] };
|
|
5115
5402
|
}
|
|
5116
5403
|
const outputDir = options.output || "./template-screenshots";
|
|
5117
|
-
await
|
|
5404
|
+
await mkdir5(outputDir, { recursive: true });
|
|
5118
5405
|
const configLoader = new ConfigLoader();
|
|
5119
5406
|
const configPath = options.config || await configLoader.findConfig(process.cwd());
|
|
5120
5407
|
const config = await configLoader.load(configPath);
|
|
@@ -5127,8 +5414,8 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5127
5414
|
process.exitCode = ExitCode.GeneralError;
|
|
5128
5415
|
return { success: false, errors: [message] };
|
|
5129
5416
|
}
|
|
5130
|
-
const tempDir =
|
|
5131
|
-
await
|
|
5417
|
+
const tempDir = join12(tmpdir2(), `slide-gen-template-screenshot-${Date.now()}`);
|
|
5418
|
+
await mkdir5(tempDir, { recursive: true });
|
|
5132
5419
|
const isAiFormat = options.format === "ai";
|
|
5133
5420
|
const imageFormat = isAiFormat ? "jpeg" : options.format || "png";
|
|
5134
5421
|
const imageWidth = isAiFormat ? 640 : options.width || 1280;
|
|
@@ -5139,9 +5426,9 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5139
5426
|
console.log(`Processing: ${template.name}`);
|
|
5140
5427
|
}
|
|
5141
5428
|
const sampleYaml = generateSampleYaml(template);
|
|
5142
|
-
const yamlPath =
|
|
5143
|
-
await
|
|
5144
|
-
const mdPath =
|
|
5429
|
+
const yamlPath = join12(tempDir, `${template.name}.yaml`);
|
|
5430
|
+
await writeFile4(yamlPath, sampleYaml);
|
|
5431
|
+
const mdPath = join12(tempDir, `${template.name}.md`);
|
|
5145
5432
|
try {
|
|
5146
5433
|
await pipeline.runWithResult(yamlPath, { outputPath: mdPath });
|
|
5147
5434
|
} catch (error) {
|
|
@@ -5158,7 +5445,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5158
5445
|
if (imageFormat === "jpeg") {
|
|
5159
5446
|
marpArgs.push("--jpeg-quality", String(options.quality || 80));
|
|
5160
5447
|
}
|
|
5161
|
-
const outputBasePath =
|
|
5448
|
+
const outputBasePath = join12(tempDir, template.name);
|
|
5162
5449
|
marpArgs.push("-o", outputBasePath);
|
|
5163
5450
|
marpArgs.push(mdPath);
|
|
5164
5451
|
try {
|
|
@@ -5178,7 +5465,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5178
5465
|
(f) => f.startsWith(template.name) && f.endsWith(`.${imageFormat}`)
|
|
5179
5466
|
);
|
|
5180
5467
|
for (const slideFile of slideFiles) {
|
|
5181
|
-
const sourceFile =
|
|
5468
|
+
const sourceFile = join12(tempDir, slideFile);
|
|
5182
5469
|
const match = slideFile.match(/\.(\d{3})\.\w+$/);
|
|
5183
5470
|
let targetName;
|
|
5184
5471
|
if (slideFiles.length === 1 || !match) {
|
|
@@ -5186,7 +5473,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5186
5473
|
} else {
|
|
5187
5474
|
targetName = slideFile;
|
|
5188
5475
|
}
|
|
5189
|
-
const targetFile =
|
|
5476
|
+
const targetFile = join12(outputDir, targetName);
|
|
5190
5477
|
const { copyFile: fsCopyFile } = await import("fs/promises");
|
|
5191
5478
|
await fsCopyFile(sourceFile, targetFile);
|
|
5192
5479
|
generatedFiles.push(targetName);
|
|
@@ -5198,10 +5485,10 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5198
5485
|
console.log("Generating contact sheet...");
|
|
5199
5486
|
const { generateContactSheet: genContactSheet } = await Promise.resolve().then(() => (init_screenshot(), screenshot_exports));
|
|
5200
5487
|
const slides = generatedFiles.map((file, index) => ({
|
|
5201
|
-
path:
|
|
5488
|
+
path: join12(outputDir, file),
|
|
5202
5489
|
index: index + 1
|
|
5203
5490
|
}));
|
|
5204
|
-
const contactSheetPath =
|
|
5491
|
+
const contactSheetPath = join12(outputDir, `templates-contact.${imageFormat === "jpeg" ? "jpeg" : "png"}`);
|
|
5205
5492
|
const contactResult = await genContactSheet(slides, {
|
|
5206
5493
|
outputPath: contactSheetPath,
|
|
5207
5494
|
columns: options.columns || 3,
|
|
@@ -5224,7 +5511,7 @@ async function executeTemplateScreenshot(name, options) {
|
|
|
5224
5511
|
console.log("Screenshots saved (AI-optimized):");
|
|
5225
5512
|
console.log("");
|
|
5226
5513
|
for (const file of generatedFiles) {
|
|
5227
|
-
console.log(` ${
|
|
5514
|
+
console.log(` ${join12(outputDir, file)}`);
|
|
5228
5515
|
}
|
|
5229
5516
|
console.log("");
|
|
5230
5517
|
console.log(`Estimated tokens: ~${totalTokens} (${generatedFiles.length} images)`);
|
|
@@ -5252,194 +5539,14 @@ function createTemplatesCommand() {
|
|
|
5252
5539
|
// src/cli/commands/icons.ts
|
|
5253
5540
|
init_registry();
|
|
5254
5541
|
init_resolver();
|
|
5542
|
+
init_fetcher();
|
|
5543
|
+
init_loader2();
|
|
5544
|
+
init_convert();
|
|
5255
5545
|
import { Command as Command6 } from "commander";
|
|
5256
5546
|
import chalk6 from "chalk";
|
|
5257
5547
|
import * as fs8 from "fs/promises";
|
|
5258
5548
|
import * as path8 from "path";
|
|
5259
5549
|
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
5550
|
function extractIconReferences(presentation) {
|
|
5444
5551
|
const icons = /* @__PURE__ */ new Set();
|
|
5445
5552
|
function extractFromValue(value) {
|
|
@@ -7014,6 +7121,73 @@ Take new screenshots and verify improvements.
|
|
|
7014
7121
|
7. \`Read ./screenshots/presentation.003.jpeg\`
|
|
7015
7122
|
8. Verify fix, move to next slide
|
|
7016
7123
|
|
|
7124
|
+
## Custom Template Creation
|
|
7125
|
+
|
|
7126
|
+
When creating or modifying templates, follow these critical rules.
|
|
7127
|
+
|
|
7128
|
+
### Critical Rules
|
|
7129
|
+
|
|
7130
|
+
#### 1. CSS Selectors (Marp Scoping)
|
|
7131
|
+
|
|
7132
|
+
When using \`<!-- _class: foo -->\`:
|
|
7133
|
+
- The class is added to \`<section>\` element itself
|
|
7134
|
+
- Use \`section.foo\` when targeting the \`<section>\` element itself
|
|
7135
|
+
|
|
7136
|
+
\`\`\`yaml
|
|
7137
|
+
# Correct
|
|
7138
|
+
output: |
|
|
7139
|
+
<!-- _class: my-slide -->
|
|
7140
|
+
<div class="container">...</div>
|
|
7141
|
+
|
|
7142
|
+
css: |
|
|
7143
|
+
section.my-slide .container { display: flex; }
|
|
7144
|
+
\`\`\`
|
|
7145
|
+
|
|
7146
|
+
\`\`\`yaml
|
|
7147
|
+
# WRONG - This does not target the section element itself
|
|
7148
|
+
css: |
|
|
7149
|
+
.my-slide .container { display: flex; }
|
|
7150
|
+
\`\`\`
|
|
7151
|
+
|
|
7152
|
+
Note: \`.my-slide .container\` is valid for descendants inside the section, but it does not target the \`<section>\` element itself.
|
|
7153
|
+
|
|
7154
|
+
#### 2. HTML + Markdown (CommonMark)
|
|
7155
|
+
|
|
7156
|
+
Markdown inside HTML is only parsed with blank lines:
|
|
7157
|
+
|
|
7158
|
+
\`\`\`yaml
|
|
7159
|
+
# Correct - blank line after <div>
|
|
7160
|
+
output: |
|
|
7161
|
+
<div class="container">
|
|
7162
|
+
|
|
7163
|
+
## Heading works
|
|
7164
|
+
- List works
|
|
7165
|
+
|
|
7166
|
+
</div>
|
|
7167
|
+
\`\`\`
|
|
7168
|
+
|
|
7169
|
+
\`\`\`yaml
|
|
7170
|
+
# WRONG - no blank line, Markdown won't parse
|
|
7171
|
+
output: |
|
|
7172
|
+
<div class="container">
|
|
7173
|
+
## This is plain text
|
|
7174
|
+
- Not a list
|
|
7175
|
+
</div>
|
|
7176
|
+
\`\`\`
|
|
7177
|
+
|
|
7178
|
+
### Visual Regression Test (Required)
|
|
7179
|
+
|
|
7180
|
+
After creating/modifying templates, ALWAYS verify with screenshots:
|
|
7181
|
+
|
|
7182
|
+
\`\`\`bash
|
|
7183
|
+
slide-gen templates screenshot <template-name> --format png -o /tmp/test
|
|
7184
|
+
\`\`\`
|
|
7185
|
+
|
|
7186
|
+
**Check that:**
|
|
7187
|
+
- Layout is correct (columns, grids, flexbox)
|
|
7188
|
+
- Markdown is parsed (headings, lists rendered properly)
|
|
7189
|
+
- CSS is not silently failing
|
|
7190
|
+
|
|
7017
7191
|
## Reference Management
|
|
7018
7192
|
|
|
7019
7193
|
For academic presentations, manage citations and references:
|
|
@@ -7312,6 +7486,92 @@ Image with text side by side.
|
|
|
7312
7486
|
- \`items\` or \`text\`: Content (required)
|
|
7313
7487
|
|
|
7314
7488
|
## Run \`slide-gen templates list --format llm\` for full list.
|
|
7489
|
+
|
|
7490
|
+
---
|
|
7491
|
+
|
|
7492
|
+
## Creating Custom Templates
|
|
7493
|
+
|
|
7494
|
+
### Template File Structure
|
|
7495
|
+
|
|
7496
|
+
\`\`\`yaml
|
|
7497
|
+
name: my-template
|
|
7498
|
+
description: "Description for LLM"
|
|
7499
|
+
category: custom
|
|
7500
|
+
|
|
7501
|
+
schema:
|
|
7502
|
+
type: object
|
|
7503
|
+
required: [title]
|
|
7504
|
+
properties:
|
|
7505
|
+
title: { type: string }
|
|
7506
|
+
content: { type: string }
|
|
7507
|
+
|
|
7508
|
+
example:
|
|
7509
|
+
title: "Sample"
|
|
7510
|
+
content: "Sample content"
|
|
7511
|
+
|
|
7512
|
+
output: |
|
|
7513
|
+
---
|
|
7514
|
+
<!-- _class: my-template -->
|
|
7515
|
+
|
|
7516
|
+
# {{ title }}
|
|
7517
|
+
|
|
7518
|
+
<div class="content">
|
|
7519
|
+
|
|
7520
|
+
{{ content }}
|
|
7521
|
+
|
|
7522
|
+
</div>
|
|
7523
|
+
|
|
7524
|
+
css: |
|
|
7525
|
+
section.my-template .content {
|
|
7526
|
+
padding: 1em;
|
|
7527
|
+
}
|
|
7528
|
+
\`\`\`
|
|
7529
|
+
|
|
7530
|
+
### Critical CSS Rule: Marp Scoping
|
|
7531
|
+
|
|
7532
|
+
When using \`<!-- _class: foo -->\`, Marp adds class to the \`<section>\` element.
|
|
7533
|
+
|
|
7534
|
+
**Correct (targeting the section element):**
|
|
7535
|
+
\`\`\`css
|
|
7536
|
+
section.foo .child { ... }
|
|
7537
|
+
\`\`\`
|
|
7538
|
+
|
|
7539
|
+
**Wrong (for targeting the section element):**
|
|
7540
|
+
\`\`\`css
|
|
7541
|
+
.foo .child { ... }
|
|
7542
|
+
\`\`\`
|
|
7543
|
+
|
|
7544
|
+
Note: \`.foo .child\` is valid for descendants inside the section, but it does not target the \`<section>\` element itself.
|
|
7545
|
+
|
|
7546
|
+
### Critical HTML Rule: CommonMark
|
|
7547
|
+
|
|
7548
|
+
Markdown inside HTML only parses with blank lines after tags:
|
|
7549
|
+
|
|
7550
|
+
**Correct:**
|
|
7551
|
+
\`\`\`html
|
|
7552
|
+
<div>
|
|
7553
|
+
|
|
7554
|
+
## Heading works
|
|
7555
|
+
|
|
7556
|
+
</div>
|
|
7557
|
+
\`\`\`
|
|
7558
|
+
|
|
7559
|
+
**Wrong:**
|
|
7560
|
+
\`\`\`html
|
|
7561
|
+
<div>
|
|
7562
|
+
## Plain text, not heading
|
|
7563
|
+
</div>
|
|
7564
|
+
\`\`\`
|
|
7565
|
+
|
|
7566
|
+
### Visual Verification
|
|
7567
|
+
|
|
7568
|
+
Always test templates with screenshots:
|
|
7569
|
+
|
|
7570
|
+
\`\`\`bash
|
|
7571
|
+
slide-gen templates screenshot <template-name> --format png -o /tmp/test
|
|
7572
|
+
\`\`\`
|
|
7573
|
+
|
|
7574
|
+
Check: Layout, Markdown parsing, CSS application.
|
|
7315
7575
|
`;
|
|
7316
7576
|
}
|
|
7317
7577
|
|
|
@@ -7989,7 +8249,8 @@ slide-gen validate presentation.yaml
|
|
|
7989
8249
|
// src/cli/commands/init.ts
|
|
7990
8250
|
function getPackageRoot() {
|
|
7991
8251
|
const __dirname = dirname8(fileURLToPath(import.meta.url));
|
|
7992
|
-
|
|
8252
|
+
const isInSourceDir = __dirname.endsWith(`${sep}src${sep}cli${sep}commands`) || __dirname.endsWith("/src/cli/commands");
|
|
8253
|
+
if (isInSourceDir) {
|
|
7993
8254
|
return join17(__dirname, "..", "..", "..");
|
|
7994
8255
|
}
|
|
7995
8256
|
return join17(__dirname, "..", "..");
|
|
@@ -8565,14 +8826,20 @@ async function executeWatch(inputPath, options) {
|
|
|
8565
8826
|
}, debounceMs);
|
|
8566
8827
|
};
|
|
8567
8828
|
state.start();
|
|
8829
|
+
const isWSL = !!process.env["WSL_DISTRO_NAME"];
|
|
8568
8830
|
watcher = chokidarWatch2(inputPath, {
|
|
8569
8831
|
persistent: true,
|
|
8570
8832
|
ignoreInitial: true,
|
|
8833
|
+
usePolling: isWSL,
|
|
8834
|
+
...isWSL && { interval: 100 },
|
|
8571
8835
|
awaitWriteFinish: {
|
|
8572
8836
|
stabilityThreshold: 100,
|
|
8573
8837
|
pollInterval: 50
|
|
8574
8838
|
}
|
|
8575
8839
|
});
|
|
8840
|
+
await new Promise((resolve8) => {
|
|
8841
|
+
watcher.on("ready", resolve8);
|
|
8842
|
+
});
|
|
8576
8843
|
watcher.on("change", handleChange);
|
|
8577
8844
|
const cleanup = () => {
|
|
8578
8845
|
state.stop();
|