coursecode 0.1.9 → 0.1.10

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/bin/cli.js CHANGED
@@ -21,23 +21,23 @@ import path from 'path';
21
21
  import fs from 'fs';
22
22
 
23
23
  // =============================================================================
24
- // CORPORATE NETWORK: System CA cert auto-injection
24
+ // CORPORATE NETWORK: System CA cert injection
25
25
  //
26
26
  // On corporate machines with SSL-inspecting proxies (e.g. Zscaler), the proxy
27
27
  // presents its own CA certificate. Node.js ships its own CA bundle and ignores
28
28
  // the OS trust store, so TLS verification fails.
29
29
  //
30
- // Fix: export the OS root cert store to a temp PEM file and re-exec with
31
- // NODE_EXTRA_CA_CERTS pointing to it. Node picks it up before any TLS
32
- // handshakes. Transparent to users completes in < 200ms.
33
- //
34
- // The NODE_EXTRA_CA_CERTS guard prevents an infinite re-exec loop.
30
+ // Windows: win-ca injects certs directly into Node's TLS context (in-process,
31
+ // no subprocess, no re-exec). Works regardless of PowerShell policy.
32
+ // macOS/Linux: exports OS certs to a temp PEM file and re-execs with
33
+ // NODE_EXTRA_CA_CERTS. The guard prevents an infinite loop.
35
34
  // =============================================================================
36
35
 
37
36
  if (!process.env.NODE_EXTRA_CA_CERTS) {
38
- const { exportSystemCerts } = await import('../lib/cloud-certs.js');
39
- const certPath = await exportSystemCerts();
37
+ const { injectSystemCerts } = await import('../lib/cloud-certs.js');
38
+ const certPath = await injectSystemCerts();
40
39
  if (certPath) {
40
+ // macOS/Linux: re-exec with NODE_EXTRA_CA_CERTS pointing to PEM file
41
41
  const { execFileSync } = await import('child_process');
42
42
  execFileSync(process.execPath, process.argv.slice(1), {
43
43
  env: { ...process.env, NODE_EXTRA_CA_CERTS: certPath },
@@ -45,6 +45,7 @@ if (!process.env.NODE_EXTRA_CA_CERTS) {
45
45
  });
46
46
  process.exit(0);
47
47
  }
48
+ // Windows: win-ca already injected certs in-process — continue normally
48
49
  }
49
50
 
50
51
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -136,6 +136,30 @@ em, i {
136
136
  color: var(--color-gray-800);
137
137
  }
138
138
 
139
+ /* Styled Scrollbars (cross-platform) */
140
+ html {
141
+ scrollbar-width: thin;
142
+ scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
143
+ }
144
+
145
+ ::-webkit-scrollbar {
146
+ width: 8px;
147
+ height: 8px;
148
+ }
149
+
150
+ ::-webkit-scrollbar-track {
151
+ background: var(--scrollbar-track);
152
+ }
153
+
154
+ ::-webkit-scrollbar-thumb {
155
+ background: var(--scrollbar-thumb);
156
+ border-radius: 4px;
157
+ }
158
+
159
+ ::-webkit-scrollbar-thumb:hover {
160
+ background: var(--scrollbar-thumb-hover);
161
+ }
162
+
139
163
  /* Link Styles */
140
164
  a {
141
165
  color: var(--color-primary);
@@ -441,6 +441,11 @@
441
441
  --sidebar-scrollbar-thumb-hover: var(--color-gray-400); /* Sidebar scrollbar thumb (hover) */
442
442
  --sidebar-backdrop: var(--bg-overlay); /* Sidebar overlay backdrop color */
443
443
 
444
+ /* Scrollbar Tokens (global, content area) */
445
+ --scrollbar-thumb: var(--color-gray-300); /* Scrollbar thumb */
446
+ --scrollbar-thumb-hover: var(--color-gray-400); /* Scrollbar thumb (hover) */
447
+ --scrollbar-track: transparent; /* Scrollbar track background */
448
+
444
449
  /* Footer Tokens */
445
450
  --footer-bg: var(--bg-surface); /* Footer background */
446
451
  --footer-text: var(--text-primary); /* Footer text color */
@@ -678,6 +683,9 @@
678
683
  --sidebar-scrollbar-thumb: var(--color-gray-500);
679
684
  --sidebar-scrollbar-thumb-hover: var(--color-gray-400);
680
685
  --sidebar-backdrop: color-mix(in srgb, var(--palette-black) 38%, transparent);
686
+ --scrollbar-thumb: var(--color-gray-500);
687
+ --scrollbar-thumb-hover: var(--color-gray-400);
688
+ --scrollbar-track: transparent;
681
689
  --footer-bg: var(--bg-surface);
682
690
  --footer-text: var(--text-primary);
683
691
  --footer-border: var(--border-default);
@@ -1,15 +1,23 @@
1
1
  /**
2
- * cloud-certs.js — System CA certificate export for corporate network compatibility.
2
+ * cloud-certs.js — System CA certificate injection for corporate network compatibility.
3
3
  *
4
- * Exports the OS trusted root store to a temp PEM file so Node.js can verify
5
- * TLS connections that pass through SSL-inspecting proxies (e.g. Zscaler).
4
+ * On corporate machines with SSL-inspecting proxies (e.g. Zscaler), the proxy
5
+ * presents its own CA certificate. Node.js ships its own CA bundle and ignores
6
+ * the OS trust store, causing TLS verification failures.
6
7
  *
7
- * Returns the path to the PEM file on success, null on failure.
8
- * Never throws silent fallback for non-corporate machines.
8
+ * Platform strategy:
9
+ * - Windows: `win-ca` (native N-API addon, calls Windows CryptoAPI directly)
10
+ * - macOS: `security` CLI exports system keychains to PEM
11
+ * - Linux: reads well-known CA bundle file paths
12
+ *
13
+ * On macOS/Linux, returns a PEM file path for NODE_EXTRA_CA_CERTS.
14
+ * On Windows, injects certs directly into Node's TLS context (no file needed).
15
+ * Never throws — silent no-op on non-corporate machines.
9
16
  */
10
17
 
11
18
  import { execFile } from 'child_process';
12
19
  import { promisify } from 'util';
20
+ import { createRequire } from 'module';
13
21
  import fs from 'fs';
14
22
  import os from 'os';
15
23
  import path from 'path';
@@ -17,25 +25,34 @@ import crypto from 'crypto';
17
25
 
18
26
  const execFileAsync = promisify(execFile);
19
27
 
20
- /** Cached result — only export once per process lifetime. */
21
- let _cachedCertPath = undefined;
28
+ /** Cached result — only run once per process lifetime. */
29
+ let _applied = false;
22
30
 
23
31
  /**
24
- * Export the OS system root certificate store to a temp PEM file.
32
+ * Inject the OS system root certificates into Node's TLS context.
33
+ *
34
+ * On Windows, win-ca patches Node's TLS in-process (no file needed, no re-exec).
35
+ * On macOS/Linux, returns a PEM file path for NODE_EXTRA_CA_CERTS re-exec.
25
36
  *
26
- * @returns {Promise<string|null>} Absolute path to the PEM file, or null if unavailable.
37
+ * @returns {Promise<string|null>} PEM file path (macOS/Linux) or null (Windows/unavailable).
27
38
  */
28
- export async function exportSystemCerts() {
29
- if (_cachedCertPath !== undefined) return _cachedCertPath;
39
+ export async function injectSystemCerts() {
40
+ if (_applied) return null;
41
+ _applied = true;
30
42
 
31
43
  try {
32
- const pem = await readSystemCerts();
33
- if (!pem || !pem.trim()) {
34
- _cachedCertPath = null;
44
+ if (process.platform === 'win32') {
45
+ injectWindowsCerts();
35
46
  return null;
36
47
  }
37
48
 
38
- // Write to a stable temp path (same content = same hash, avoids accumulation)
49
+ // macOS/Linux: export to PEM file for NODE_EXTRA_CA_CERTS
50
+ const pem = process.platform === 'darwin'
51
+ ? await readMacosCerts()
52
+ : readLinuxCerts();
53
+
54
+ if (!pem || !pem.trim()) return null;
55
+
39
56
  const hash = crypto.createHash('sha1').update(pem).digest('hex').slice(0, 8);
40
57
  const certPath = path.join(os.tmpdir(), `coursecode-ca-${hash}.pem`);
41
58
 
@@ -43,31 +60,26 @@ export async function exportSystemCerts() {
43
60
  fs.writeFileSync(certPath, pem, { mode: 0o600 });
44
61
  }
45
62
 
46
- _cachedCertPath = certPath;
47
63
  return certPath;
48
64
  } catch {
49
- _cachedCertPath = null;
50
65
  return null;
51
66
  }
52
67
  }
53
68
 
54
69
  /**
55
- * Read system certificates as a PEM string.
56
- * Platform-specific — returns null if the platform is unsupported or export fails.
70
+ * Windows: inject system root certs via win-ca.
71
+ *
72
+ * win-ca is a native N-API addon that calls Windows CryptoAPI directly.
73
+ * No PowerShell, no subprocesses, no temp files. Works regardless of
74
+ * execution policy, AppLocker, or PowerShell availability.
75
+ *
76
+ * The { inject: '+' } mode patches tls.createSecureContext() so system
77
+ * certs are used *in addition to* Node's built-in CA bundle.
57
78
  */
58
- async function readSystemCerts() {
59
- const platform = process.platform;
60
-
61
- if (platform === 'darwin') {
62
- return readMacosCerts();
63
- }
64
-
65
- if (platform === 'win32') {
66
- return readWindowsCerts();
67
- }
68
-
69
- // Linux/other: read well-known bundle paths directly
70
- return readLinuxCerts();
79
+ function injectWindowsCerts() {
80
+ const require = createRequire(import.meta.url);
81
+ const winCa = require('win-ca/api');
82
+ winCa({ inject: '+' });
71
83
  }
72
84
 
73
85
  /**
@@ -75,7 +87,6 @@ async function readSystemCerts() {
75
87
  * This includes all roots installed via Apple MDM / System Preferences.
76
88
  */
77
89
  async function readMacosCerts() {
78
- // Export from all common keychains — ignore errors on missing keychains
79
90
  const keychains = [
80
91
  '/Library/Keychains/SystemRootCertificates.keychain',
81
92
  '/System/Library/Keychains/SystemRootCertificates.keychain',
@@ -97,28 +108,6 @@ async function readMacosCerts() {
97
108
  return pems.join('\n');
98
109
  }
99
110
 
100
- /**
101
- * Windows: export LocalMachine\Root via PowerShell.
102
- * This includes certs deployed via Group Policy / MDM.
103
- */
104
- async function readWindowsCerts() {
105
- const script = [
106
- '$certs = Get-ChildItem -Path Cert:\\LocalMachine\\Root',
107
- '$pems = $certs | ForEach-Object {',
108
- ' $bytes = $_.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)',
109
- ' $b64 = [System.Convert]::ToBase64String($bytes, "InsertLineBreaks")',
110
- ' "-----BEGIN CERTIFICATE-----`n" + $b64 + "`n-----END CERTIFICATE-----"',
111
- '}',
112
- '$pems -join "`n"',
113
- ].join('; ');
114
-
115
- const { stdout } = await execFileAsync('powershell', [
116
- '-NoProfile', '-NonInteractive', '-Command', script,
117
- ], { maxBuffer: 16 * 1024 * 1024 });
118
-
119
- return stdout;
120
- }
121
-
122
111
  /**
123
112
  * Linux: read the system CA bundle from well-known locations.
124
113
  * No subprocess needed — just read the file directly.
package/lib/cloud.js CHANGED
@@ -893,21 +893,18 @@ export async function status() {
893
893
  // =============================================================================
894
894
 
895
895
  /**
896
- * Zip a directory's contents using the system `zip` command.
897
- * Falls back to a tar+gzip approach if zip isn't available.
896
+ * Zip a directory's contents using archiver (cross-platform, no native tools needed).
898
897
  */
899
- function zipDirectory(sourceDir, outputPath) {
898
+ async function zipDirectory(sourceDir, outputPath) {
899
+ const archiver = (await import('archiver')).default;
900
900
  return new Promise((resolve, reject) => {
901
- // Use system zip: cd into dir so paths are relative
902
- exec(
903
- `cd "${sourceDir}" && zip -r -q "${outputPath}" .`,
904
- (error) => {
905
- if (error) {
906
- reject(new Error(`Failed to create zip: ${error.message}. Ensure 'zip' is installed.`));
907
- } else {
908
- resolve();
909
- }
910
- }
911
- );
901
+ const output = fs.createWriteStream(outputPath);
902
+ const archive = archiver('zip', { zlib: { level: 9 } });
903
+
904
+ output.on('close', () => resolve());
905
+ archive.on('error', reject);
906
+ archive.pipe(output);
907
+ archive.directory(sourceDir, false);
908
+ archive.finalize();
912
909
  });
913
910
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coursecode",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Multi-format course authoring framework with CLI tools (SCORM 2004, SCORM 1.2, cmi5, LTI 1.3)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -99,6 +99,7 @@
99
99
  },
100
100
  "dependencies": {
101
101
  "@modelcontextprotocol/sdk": "^1.0.0",
102
+ "archiver": "^7.0.1",
102
103
  "commander": "^14.0.3",
103
104
  "lz-string": "^1.5.0",
104
105
  "mammoth": "^1.8.0",
@@ -108,6 +109,7 @@
108
109
  "pdf2json": "^4.0.2",
109
110
  "pdf2md": "^1.0.2",
110
111
  "puppeteer-core": "^24.37.2",
112
+ "win-ca": "^3.5.1",
111
113
  "ws": "^8.18.0"
112
114
  },
113
115
  "devDependencies": {
@@ -115,7 +117,6 @@
115
117
  "@vitest/coverage-v8": "^4.0.18",
116
118
  "@xapi/cmi5": "^1.4.0",
117
119
  "acorn": "^8.15.0",
118
- "archiver": "^7.0.1",
119
120
  "eslint": "^10.0.0",
120
121
  "globals": "^17.3.0",
121
122
  "jose": "^6.1.3",