scorm-review 1.0.0 → 1.1.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.
Files changed (2) hide show
  1. package/dist/cli.js +143 -58
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2894,8 +2894,8 @@ var require_utils = __commonJS({
2894
2894
  var result = transform[inputType][outputType](input);
2895
2895
  return result;
2896
2896
  };
2897
- exports2.resolve = function(path2) {
2898
- var parts = path2.split("/");
2897
+ exports2.resolve = function(path3) {
2898
+ var parts = path3.split("/");
2899
2899
  var result = [];
2900
2900
  for (var index = 0; index < parts.length; index++) {
2901
2901
  var part = parts[index];
@@ -8725,18 +8725,18 @@ var require_object = __commonJS({
8725
8725
  var object = new ZipObject(name, zipObjectContent, o);
8726
8726
  this.files[name] = object;
8727
8727
  };
8728
- var parentFolder = function(path2) {
8729
- if (path2.slice(-1) === "/") {
8730
- path2 = path2.substring(0, path2.length - 1);
8728
+ var parentFolder = function(path3) {
8729
+ if (path3.slice(-1) === "/") {
8730
+ path3 = path3.substring(0, path3.length - 1);
8731
8731
  }
8732
- var lastSlash = path2.lastIndexOf("/");
8733
- return lastSlash > 0 ? path2.substring(0, lastSlash) : "";
8732
+ var lastSlash = path3.lastIndexOf("/");
8733
+ return lastSlash > 0 ? path3.substring(0, lastSlash) : "";
8734
8734
  };
8735
- var forceTrailingSlash = function(path2) {
8736
- if (path2.slice(-1) !== "/") {
8737
- path2 += "/";
8735
+ var forceTrailingSlash = function(path3) {
8736
+ if (path3.slice(-1) !== "/") {
8737
+ path3 += "/";
8738
8738
  }
8739
- return path2;
8739
+ return path3;
8740
8740
  };
8741
8741
  var folderAdd = function(name, createFolders) {
8742
8742
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -9702,9 +9702,9 @@ var require_load = __commonJS({
9702
9702
  var require_lib3 = __commonJS({
9703
9703
  "node_modules/jszip/lib/index.js"(exports2, module2) {
9704
9704
  "use strict";
9705
- function JSZip2() {
9706
- if (!(this instanceof JSZip2)) {
9707
- return new JSZip2();
9705
+ function JSZip3() {
9706
+ if (!(this instanceof JSZip3)) {
9707
+ return new JSZip3();
9708
9708
  }
9709
9709
  if (arguments.length) {
9710
9710
  throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
@@ -9713,7 +9713,7 @@ var require_lib3 = __commonJS({
9713
9713
  this.comment = null;
9714
9714
  this.root = "";
9715
9715
  this.clone = function() {
9716
- var newObj = new JSZip2();
9716
+ var newObj = new JSZip3();
9717
9717
  for (var i in this) {
9718
9718
  if (typeof this[i] !== "function") {
9719
9719
  newObj[i] = this[i];
@@ -9722,25 +9722,28 @@ var require_lib3 = __commonJS({
9722
9722
  return newObj;
9723
9723
  };
9724
9724
  }
9725
- JSZip2.prototype = require_object();
9726
- JSZip2.prototype.loadAsync = require_load();
9727
- JSZip2.support = require_support();
9728
- JSZip2.defaults = require_defaults();
9729
- JSZip2.version = "3.10.1";
9730
- JSZip2.loadAsync = function(content, options) {
9731
- return new JSZip2().loadAsync(content, options);
9725
+ JSZip3.prototype = require_object();
9726
+ JSZip3.prototype.loadAsync = require_load();
9727
+ JSZip3.support = require_support();
9728
+ JSZip3.defaults = require_defaults();
9729
+ JSZip3.version = "3.10.1";
9730
+ JSZip3.loadAsync = function(content, options) {
9731
+ return new JSZip3().loadAsync(content, options);
9732
9732
  };
9733
- JSZip2.external = require_external();
9734
- module2.exports = JSZip2;
9733
+ JSZip3.external = require_external();
9734
+ module2.exports = JSZip3;
9735
9735
  }
9736
9736
  });
9737
9737
 
9738
9738
  // src/cli.ts
9739
- var import_fs = __toESM(require("fs"));
9740
- var import_path = __toESM(require("path"));
9739
+ var import_fs2 = __toESM(require("fs"));
9740
+ var import_path2 = __toESM(require("path"));
9741
+ var import_jszip2 = __toESM(require_lib3());
9741
9742
 
9742
9743
  // src/bundler.ts
9743
9744
  var import_jszip = __toESM(require_lib3());
9745
+ var import_fs = __toESM(require("fs"));
9746
+ var import_path = __toESM(require("path"));
9744
9747
 
9745
9748
  // src/bundler-template.ts
9746
9749
  var UNPACKER_SCRIPT = `
@@ -9932,6 +9935,56 @@ async function bundleScormZip(zipBuffer, annotateScript) {
9932
9935
  processedHtml = stripExistingBundle(processedHtml);
9933
9936
  return buildOutputHtml(processedHtml, manifest, annotateScript);
9934
9937
  }
9938
+ async function bundleScormFolder(folderPath, annotateScript) {
9939
+ function collectFiles(dir, baseDir) {
9940
+ const results = [];
9941
+ for (const entry of import_fs.default.readdirSync(dir)) {
9942
+ const fullPath = import_path.default.join(dir, entry);
9943
+ const stat = import_fs.default.statSync(fullPath);
9944
+ if (stat.isDirectory()) {
9945
+ results.push(...collectFiles(fullPath, baseDir));
9946
+ } else {
9947
+ results.push(fullPath);
9948
+ }
9949
+ }
9950
+ return results;
9951
+ }
9952
+ const allFiles = collectFiles(folderPath, folderPath);
9953
+ const relFiles = allFiles.map((f) => import_path.default.relative(folderPath, f).replace(/\\/g, "/"));
9954
+ function findEntryHtmlInFiles(files) {
9955
+ if (files.includes("index.html")) return "index.html";
9956
+ if (files.includes("Index.html")) return "Index.html";
9957
+ const nested = files.find((f) => /^[^/]+\/index\.html$/i.test(f));
9958
+ if (nested) return nested;
9959
+ const anyHtml = files.find((f) => /\.html?$/i.test(f));
9960
+ return anyHtml ?? null;
9961
+ }
9962
+ const entryRelPath = findEntryHtmlInFiles(relFiles);
9963
+ if (!entryRelPath) throw new Error("No HTML entry point found in the folder.");
9964
+ const entryAbsPath = import_path.default.join(folderPath, entryRelPath);
9965
+ const templateHtml = import_fs.default.readFileSync(entryAbsPath, "utf-8");
9966
+ const entryDir = entryRelPath.includes("/") ? entryRelPath.slice(0, entryRelPath.lastIndexOf("/") + 1) : "";
9967
+ const manifest = {};
9968
+ const uuidMap = {};
9969
+ for (const relFile of relFiles) {
9970
+ if (relFile === entryRelPath) continue;
9971
+ if (relFile.endsWith("imsmanifest.xml")) continue;
9972
+ if (relFile.endsWith(".bat")) continue;
9973
+ if (import_path.default.basename(relFile) === ".DS_Store") continue;
9974
+ const absPath = import_path.default.join(folderPath, relFile);
9975
+ const mime = getMime(relFile);
9976
+ const data = import_fs.default.readFileSync(absPath).toString("base64");
9977
+ const uuid = makeUUID();
9978
+ manifest[uuid] = { mime, data, compressed: false };
9979
+ const normalizedRel = relFile.replace(/\\/g, "/");
9980
+ const relPath = entryDir && normalizedRel.startsWith(entryDir) ? normalizedRel.slice(entryDir.length) : normalizedRel;
9981
+ uuidMap[relPath] = uuid;
9982
+ if (relPath !== normalizedRel) uuidMap[normalizedRel] = uuid;
9983
+ }
9984
+ let processedHtml = replaceAssetRefs(templateHtml, uuidMap);
9985
+ processedHtml = stripExistingBundle(processedHtml);
9986
+ return buildOutputHtml(processedHtml, manifest, annotateScript);
9987
+ }
9935
9988
  function bundlePlainHtml(htmlBuffer, annotateScript) {
9936
9989
  let html = htmlBuffer.toString("utf-8");
9937
9990
  html = stripExistingBundle(html);
@@ -12838,25 +12891,36 @@ function printHelp() {
12838
12891
  scorm-review \u2014 Convert SCORM packages to self-contained review HTML files
12839
12892
 
12840
12893
  Usage:
12841
- scorm-review <input> Convert a .zip or .html file
12842
- scorm-review <input> -o <output> Specify output file path
12894
+ scorm-review <input> Convert a .zip, folder, or .html file
12895
+ scorm-review <input> -o <output> Specify output base name (no extension)
12843
12896
  scorm-review --help Show this help message
12844
12897
 
12845
12898
  Examples:
12846
12899
  scorm-review ./my-course.zip
12847
- scorm-review ./my-course.zip -o ./review.html
12900
+ scorm-review ./my-course/
12848
12901
  scorm-review ./page.html
12902
+ scorm-review ./my-course.zip -o ./output/my-course
12849
12903
 
12850
- Output:
12851
- By default, the output file is saved next to the input file with
12852
- "_review_file" appended to the name, e.g. my-course_review_file.html
12904
+ Output (zip or folder input):
12905
+ <name>_review_file.html Self-contained review HTML
12906
+ <name>_review.zip Zip file containing the HTML (for easy sharing)
12853
12907
  `);
12854
12908
  }
12855
- function resolveOutputPath(inputFile, outputFlag) {
12856
- if (outputFlag) return import_path.default.resolve(outputFlag);
12857
- const dir = import_path.default.dirname(import_path.default.resolve(inputFile));
12858
- const base = import_path.default.basename(inputFile, import_path.default.extname(inputFile));
12859
- return import_path.default.join(dir, `${base}_review_file.html`);
12909
+ function resolveOutputBase(inputPath, outputFlag) {
12910
+ if (outputFlag) return import_path2.default.resolve(outputFlag);
12911
+ const resolved = import_path2.default.resolve(inputPath);
12912
+ const dir = import_path2.default.dirname(resolved);
12913
+ const base = import_path2.default.basename(resolved.replace(/[\\/]+$/, ""), import_path2.default.extname(resolved));
12914
+ return import_path2.default.join(dir, base);
12915
+ }
12916
+ async function createReviewZip(htmlContent, htmlFilename) {
12917
+ const zip = new import_jszip2.default();
12918
+ zip.file(htmlFilename, htmlContent);
12919
+ return zip.generateAsync({
12920
+ type: "nodebuffer",
12921
+ compression: "DEFLATE",
12922
+ compressionOptions: { level: 6 }
12923
+ });
12860
12924
  }
12861
12925
  async function main() {
12862
12926
  const args = process.argv.slice(2);
@@ -12864,50 +12928,71 @@ async function main() {
12864
12928
  printHelp();
12865
12929
  process.exit(0);
12866
12930
  }
12867
- let inputFile;
12868
- let outputFile;
12931
+ let inputPath;
12932
+ let outputFlag;
12869
12933
  for (let i = 0; i < args.length; i++) {
12870
12934
  if (args[i] === "-o" || args[i] === "--output") {
12871
- outputFile = args[i + 1];
12935
+ outputFlag = args[i + 1];
12872
12936
  i++;
12873
12937
  } else {
12874
- inputFile = args[i];
12938
+ inputPath = args[i];
12875
12939
  }
12876
12940
  }
12877
- if (!inputFile) {
12878
- console.error("Error: No input file specified.\n");
12941
+ if (!inputPath) {
12942
+ console.error("Error: No input specified.\n");
12879
12943
  printHelp();
12880
12944
  process.exit(1);
12881
12945
  }
12882
- const resolvedInput = import_path.default.resolve(inputFile);
12883
- if (!import_fs.default.existsSync(resolvedInput)) {
12884
- console.error(`Error: File not found: ${resolvedInput}`);
12946
+ const resolvedInput = import_path2.default.resolve(inputPath.replace(/[\\/]+$/, ""));
12947
+ if (!import_fs2.default.existsSync(resolvedInput)) {
12948
+ console.error(`Error: Path not found: ${resolvedInput}`);
12885
12949
  process.exit(1);
12886
12950
  }
12887
- const ext = import_path.default.extname(resolvedInput).toLowerCase();
12888
- if (![".zip", ".html", ".htm"].includes(ext)) {
12889
- console.error(`Error: Unsupported file type "${ext}". Only .zip, .html, and .htm are supported.`);
12951
+ const stat = import_fs2.default.statSync(resolvedInput);
12952
+ const isFolder = stat.isDirectory();
12953
+ const ext = isFolder ? "" : import_path2.default.extname(resolvedInput).toLowerCase();
12954
+ if (!isFolder && ![".zip", ".html", ".htm"].includes(ext)) {
12955
+ console.error(`Error: Unsupported file type "${ext}". Only .zip, .html, .htm, or a folder are supported.`);
12890
12956
  process.exit(1);
12891
12957
  }
12892
- const resolvedOutput = resolveOutputPath(resolvedInput, outputFile);
12893
- console.log(`Converting: ${import_path.default.basename(resolvedInput)}`);
12894
- console.log(`Output: ${resolvedOutput}`);
12958
+ const outputBase = resolveOutputBase(resolvedInput, outputFlag);
12959
+ const htmlFilename = import_path2.default.basename(outputBase) + "_review_file.html";
12960
+ const htmlOutputPath = outputBase + "_review_file.html";
12961
+ const zipOutputPath = outputBase + "_review.zip";
12962
+ console.log(`Converting: ${import_path2.default.basename(resolvedInput)}${isFolder ? "/" : ""}`);
12963
+ console.log(`HTML: ${htmlOutputPath}`);
12964
+ if (!ext || ext === ".zip") {
12965
+ console.log(`ZIP: ${zipOutputPath}`);
12966
+ }
12895
12967
  console.log("");
12968
+ import_fs2.default.mkdirSync(import_path2.default.dirname(outputBase), { recursive: true });
12896
12969
  try {
12897
- const fileBuffer = import_fs.default.readFileSync(resolvedInput);
12898
12970
  let outputHtml;
12899
- if (ext === ".zip") {
12900
- process.stdout.write("Processing SCORM package...");
12971
+ if (isFolder) {
12972
+ process.stdout.write("Processing SCORM folder...");
12973
+ outputHtml = await bundleScormFolder(resolvedInput, ANNOTATE_SCRIPT);
12974
+ } else if (ext === ".zip") {
12975
+ process.stdout.write("Processing SCORM zip...");
12976
+ const fileBuffer = import_fs2.default.readFileSync(resolvedInput);
12901
12977
  outputHtml = await bundleScormZip(fileBuffer, ANNOTATE_SCRIPT);
12902
12978
  } else {
12903
12979
  process.stdout.write("Processing HTML file...");
12980
+ const fileBuffer = import_fs2.default.readFileSync(resolvedInput);
12904
12981
  outputHtml = bundlePlainHtml(fileBuffer, ANNOTATE_SCRIPT);
12905
12982
  }
12906
12983
  console.log(" done.");
12907
- import_fs.default.writeFileSync(resolvedOutput, outputHtml, "utf-8");
12908
- const sizeMb = (Buffer.byteLength(outputHtml, "utf-8") / 1024 / 1024).toFixed(1);
12984
+ import_fs2.default.writeFileSync(htmlOutputPath, outputHtml, "utf-8");
12985
+ const htmlSizeMb = (Buffer.byteLength(outputHtml, "utf-8") / 1024 / 1024).toFixed(1);
12909
12986
  console.log(`
12910
- \u2713 Created: ${resolvedOutput} (${sizeMb} MB)`);
12987
+ \u2713 HTML: ${htmlOutputPath} (${htmlSizeMb} MB)`);
12988
+ if (isFolder || ext === ".zip") {
12989
+ process.stdout.write("Creating review zip...");
12990
+ const zipBuffer = await createReviewZip(outputHtml, htmlFilename);
12991
+ import_fs2.default.writeFileSync(zipOutputPath, zipBuffer);
12992
+ const zipSizeMb = (zipBuffer.length / 1024 / 1024).toFixed(1);
12993
+ console.log(` done.`);
12994
+ console.log(`\u2713 ZIP: ${zipOutputPath} (${zipSizeMb} MB)`);
12995
+ }
12911
12996
  } catch (err) {
12912
12997
  const msg = err instanceof Error ? err.message : String(err);
12913
12998
  console.error(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scorm-review",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Convert SCORM zip packages into self-contained review HTML files",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {