icoa-cli 2.19.94 → 2.19.96
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/index.js +46 -4
- package/dist/repl.js +31 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -117,10 +117,35 @@ program
|
|
|
117
117
|
// wondering why "哪个命令..." appears as "????..." on their machine.
|
|
118
118
|
const envLang = process.env.LANG || process.env.LC_ALL || process.env.LC_CTYPE || '';
|
|
119
119
|
if (!/UTF-?8/i.test(envLang)) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
if (process.platform === 'win32') {
|
|
121
|
+
// Windows cmd.exe default code page (437 US, 936 CN, etc.) mangles
|
|
122
|
+
// non-Latin script. Fix is `chcp 65001`, not Unix-style `export LANG`.
|
|
123
|
+
// Skip the warning if the code page is already 65001 (PowerShell +
|
|
124
|
+
// Windows Terminal often set it automatically).
|
|
125
|
+
let codepage = '';
|
|
126
|
+
try {
|
|
127
|
+
const { execFileSync } = await import('node:child_process');
|
|
128
|
+
codepage = execFileSync('chcp.com', [], {
|
|
129
|
+
encoding: 'utf-8',
|
|
130
|
+
timeout: 1500,
|
|
131
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
132
|
+
}).trim();
|
|
133
|
+
}
|
|
134
|
+
catch { /* chcp.com unavailable or timed out */ }
|
|
135
|
+
if (!codepage.includes('65001')) {
|
|
136
|
+
console.log(chalk.yellow('⚠ Windows terminal is not using UTF-8 (current: ' + (codepage || 'unknown') + ').'));
|
|
137
|
+
console.log(chalk.gray(' Non-English text (Ukrainian, Chinese, Japanese, etc.) may show as "?" or garbled glyphs.'));
|
|
138
|
+
console.log(chalk.gray(' Fix (run before ') + chalk.cyan('icoa') + chalk.gray('): ') + chalk.cyan('chcp 65001'));
|
|
139
|
+
console.log(chalk.gray(' Or stay in English inside the CLI: ') + chalk.cyan('lang en'));
|
|
140
|
+
console.log();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.log(chalk.yellow('⚠ Your terminal locale is not UTF-8 (LANG=' + (envLang || '(unset)') + ').'));
|
|
145
|
+
console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.'));
|
|
146
|
+
console.log(chalk.gray(' Fix: ') + chalk.cyan('export LANG=en_US.UTF-8') + chalk.gray(' (or your locale, e.g. ') + chalk.cyan('zh_CN.UTF-8') + chalk.gray(', ') + chalk.cyan('uk_UA.UTF-8') + chalk.gray(')'));
|
|
147
|
+
console.log();
|
|
148
|
+
}
|
|
124
149
|
}
|
|
125
150
|
console.log(BANNER);
|
|
126
151
|
// If running interactively (no extra args or --resume), start REPL
|
|
@@ -181,4 +206,21 @@ program
|
|
|
181
206
|
console.log();
|
|
182
207
|
}
|
|
183
208
|
});
|
|
209
|
+
// Proctor-only escape hatch: `ICOA_RESET_STATE=1 icoa` wipes any abandoned
|
|
210
|
+
// exam state *before* any subcommand or REPL runs. Fires for ALL invocations
|
|
211
|
+
// (including `icoa exam <token>`) so proctors have a one-liner recovery that
|
|
212
|
+
// works regardless of which subcommand the student would run next.
|
|
213
|
+
// The token itself is untouched server-side.
|
|
214
|
+
if (process.env.ICOA_RESET_STATE === '1') {
|
|
215
|
+
try {
|
|
216
|
+
const { clearExamState } = await import('./lib/exam-state.js');
|
|
217
|
+
clearExamState();
|
|
218
|
+
console.log(chalk.yellow('⚠ ICOA_RESET_STATE=1 — local exam state wiped.'));
|
|
219
|
+
console.log(chalk.gray(' (Token NOT revoked server-side. Re-enter a fresh token with `exam <token>`.)'));
|
|
220
|
+
console.log();
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
console.log(chalk.red('⚠ ICOA_RESET_STATE: could not clear state — ') + chalk.gray(String(e)));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
184
226
|
program.parse();
|
package/dist/repl.js
CHANGED
|
@@ -106,17 +106,29 @@ function checkPython() {
|
|
|
106
106
|
// Probe python3.12 first — macOS Homebrew installs python@3.12 alongside a
|
|
107
107
|
// newer default python3, and we don't want to flag a 3.12-ready machine just
|
|
108
108
|
// because the default alias moved to 3.13.
|
|
109
|
+
// On Windows (cmd/PowerShell), the binary is `python` not `python3`, and the
|
|
110
|
+
// Python launcher `py -3` is also common. Include these so Windows K-12
|
|
111
|
+
// students don't get a spurious "Python missing" warning when Python IS
|
|
112
|
+
// installed. We also stdio: ignore stderr so cmd.exe's "not recognized as
|
|
113
|
+
// internal or external command" error doesn't leak to the user.
|
|
109
114
|
const probes = [
|
|
110
115
|
'python3.12 --version',
|
|
111
116
|
'/opt/homebrew/opt/python@3.12/bin/python3.12 --version',
|
|
112
117
|
'/usr/local/opt/python@3.12/bin/python3.12 --version',
|
|
113
118
|
'python3 --version',
|
|
119
|
+
'python --version', // Windows default name
|
|
120
|
+
'py -3.12 --version', // Windows Python Launcher
|
|
121
|
+
'py -3 --version', // Windows Python Launcher (any 3.x)
|
|
114
122
|
];
|
|
115
123
|
let lastVersion = '';
|
|
116
124
|
let lastStatus = 'missing';
|
|
117
125
|
for (const cmd of probes) {
|
|
118
126
|
try {
|
|
119
|
-
const out = execSyncFn(cmd, {
|
|
127
|
+
const out = execSyncFn(cmd, {
|
|
128
|
+
encoding: 'utf-8',
|
|
129
|
+
timeout: 2000,
|
|
130
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
131
|
+
}).trim();
|
|
120
132
|
const version = out.replace('Python ', '');
|
|
121
133
|
const [maj, min] = version.split('.').map(Number);
|
|
122
134
|
if (maj === 3 && min === 12)
|
|
@@ -905,6 +917,24 @@ export async function startRepl(program, resumeMode) {
|
|
|
905
917
|
.replace(/^python3?\s/, `${py12} `)
|
|
906
918
|
.replace(/^(python3|python)$/, py12);
|
|
907
919
|
}
|
|
920
|
+
else if (process.platform === 'win32') {
|
|
921
|
+
// Windows: the binary is `python` (not `python3`). Python Launcher `py -3`
|
|
922
|
+
// is also common. Rewrite `python3 xyz` → `python xyz` or `py -3 xyz` so
|
|
923
|
+
// students who followed Unix-oriented practical-question hints don't get
|
|
924
|
+
// "'python3' is not recognized". Prefer `py -3` if the launcher exists
|
|
925
|
+
// (handles multi-version installs); fall back to plain `python`.
|
|
926
|
+
const pyCmd = (() => {
|
|
927
|
+
try {
|
|
928
|
+
execSyncFn('py -3 --version', { stdio: ['ignore', 'ignore', 'ignore'], timeout: 1500 });
|
|
929
|
+
return 'py -3';
|
|
930
|
+
}
|
|
931
|
+
catch { }
|
|
932
|
+
return 'python';
|
|
933
|
+
})();
|
|
934
|
+
resolvedInput = resolvedInput
|
|
935
|
+
.replace(/^python3?(\.\d+)?\s/, `${pyCmd} `)
|
|
936
|
+
.replace(/^python3?(\.\d+)?$/, pyCmd);
|
|
937
|
+
}
|
|
908
938
|
else {
|
|
909
939
|
// Linux/WSL: python → python3 (or python3.12 if available)
|
|
910
940
|
const py12 = (() => { try {
|