gwchq-textjam 0.4.2 → 0.5.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/README.md +17 -43
- package/dist/360.index.js +1 -1
- package/dist/assets/{PyodideWorkerb1409749c37aedc47dc8.js → PyodideWorkereba24d5259c22fbe422a.js} +66 -48
- package/dist/assets/pyodide-stdin-sw.js +7 -0
- package/dist/index.js +45688 -31750
- package/dist/style.css +38 -28
- package/package.json +22 -16
package/README.md
CHANGED
|
@@ -91,54 +91,28 @@ Automated unit tests can be run via the `yarn test` command. These unit tests ar
|
|
|
91
91
|
|
|
92
92
|
## Publishing to npm
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
Publishing is handled by the **"NPM Publish (Scan + Bump + Publish)"** GitHub Actions workflow (`.github/workflows/publish.yml`) — there is no manual `npm publish` step.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
### Running the workflow
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
1. Go to the repo's **Actions** tab → **NPM Publish (Scan + Bump + Publish)** → **Run workflow**.
|
|
99
|
+
2. Fill in the inputs:
|
|
100
|
+
- `git_branch` — the branch to scan, bump, tag, and publish from (defaults to the current release branch).
|
|
101
|
+
- `version_bump_type` — `patch`, `minor`, or `major`, following [semantic versioning](https://semver.org/).
|
|
102
|
+
- `run_publish` — leave checked to publish to npm, or uncheck for a scan + version-bump dry run.
|
|
103
|
+
3. Run it. The workflow will:
|
|
104
|
+
- Run the shared dependency, secrets, and SAST scans (`security-scan` → `secrets-scan` → `sast-scan`).
|
|
105
|
+
- Run the test suite (`test`).
|
|
106
|
+
- Bump `package.json`, commit, tag (`vX.Y.Z`), and push to `git_branch` (`bump-version`).
|
|
107
|
+
- Build the library (via the `prepublishOnly` hook) and run `npm publish` (`publish`).
|
|
101
108
|
|
|
102
|
-
|
|
109
|
+
Each stage gates the next — if a scan or the test suite fails, the version is never bumped and nothing is published.
|
|
103
110
|
|
|
104
|
-
|
|
111
|
+
### Verify publication
|
|
105
112
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- Patch: `0.1.2` → `0.1.3` (bug fixes)
|
|
110
|
-
- Minor: `0.1.2` → `0.2.0` (new features, backwards compatible)
|
|
111
|
-
- Major: `0.1.2` → `1.0.0` (breaking changes)
|
|
112
|
-
2. **Build the library** (automatically runs via `prepublishOnly` hook):
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
yarn build:lib
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
This creates the `dist` folder with:
|
|
119
|
-
|
|
120
|
-
- `index.js` - The main ESM module
|
|
121
|
-
- `style.css` - Extracted stylesheet
|
|
122
|
-
- `assets/` - Static assets (icons, fonts, etc.)
|
|
123
|
-
|
|
124
|
-
3. **Verify the build output**:
|
|
125
|
-
|
|
126
|
-
- Check that `dist/index.js` exists and is properly built
|
|
127
|
-
- Verify `dist/style.css` contains all styles
|
|
128
|
-
- Ensure all required assets are in `dist/assets/`
|
|
129
|
-
|
|
130
|
-
4. **Publish to npm**:
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
npm publish
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
The `prepublishOnly` script will automatically run `yarn build:lib` before publishing.
|
|
137
|
-
|
|
138
|
-
5. **Verify publication**:
|
|
139
|
-
```bash
|
|
140
|
-
npm view gwchq-textjam version
|
|
141
|
-
```
|
|
113
|
+
```bash
|
|
114
|
+
npm view gwchq-textjam version
|
|
115
|
+
```
|
|
142
116
|
|
|
143
117
|
### What Gets Published
|
|
144
118
|
|
package/dist/360.index.js
CHANGED
|
@@ -5,7 +5,7 @@ export const modules = {
|
|
|
5
5
|
/***/ 22796:
|
|
6
6
|
/***/ ((module) => {
|
|
7
7
|
|
|
8
|
-
module.exports = /*#__PURE__*/JSON.parse('{"modal":{"close":"Close","error":{"error":"Error","heading":"An error has occurred","externalLink":{"message":"Unfortunately links to external sites are not available in the Editor."}}},"editorPanel":{"ariaLabel":"editor text input","characterLimitError":"Error: Character limit reached","characterLimitExplanation_one":"Files in the editor are limited to {{count}} character","characterLimitExplanation_other":"Files in the editor are limited to {{count}} characters","viewOnly":"View only","close":"close"},"filePanel":{"errors":{"reservedFileName":"{{fileName}} is a reserved file name. Please choose a different name.","containsSpaces":"File names must not contain spaces.","generalError":"Error","notUnique":"File names must be unique.","or":"or","unsupportedExtension":"File names must end in {{allowedExtensions}}."},"files":"Project files","images":"Image gallery","newFileButton":"Add file","newFileModal":{"cancel":"Cancel","heading":"Add a new file to your project","helpText":"Remember to add the file extension at the end of your file name, for example, {{examples}}","helpTextExample":{"html":"\'file.html\', \'file.css\' or \'file.js\'","python":"\'file.py\'"},"inputLabel":"Name your file","addFile":"Add file"},"renameFileModal":{"cancel":"Cancel","heading":"Rename file","inputLabel":"Name your file","save":"Save"},"fileMenu":{"label":"Open file menu","renameItem":"Rename file"}},"footer":{"accessibility":"Accessibility","charityNameAndNumber":"Raspberry Pi Foundation UK registered charity 1129409","cookies":"Cookies","privacy":"Privacy","safeguarding":"Safeguarding"},"projectName":{"label":"Project name","newProject":"New Project"},"header":{"download":"Download","editorLogoAltText":"Editor logo","projects":"Your projects","renameProject":"Edit project name","renameSave":"Save project name","save":"Save","loginToSave":"Log in to save","settings":"Settings"},"projectsPanel":{"projects":"Projects","yourProjectsButton":"Go to your projects","projectTypeLabel":"Project type"},"settingsPanel":{"info":"Settings"},"input":{"comment":{"py5":"Py5: imported mode"}},"mobile":{"code":"Code","menu":"Menu","output":"Output","preview":"Preview","steps":"Steps"},"modals":{"close":"Close"},"notifications":{"close":"close","loginPrompt":"To save this project and access it later, don\'t forget to log in or sign up!","projectRenamed":"You have renamed your project.","savePrompt":"Save this project to access it later under \\"Your projects\\"."},"project":{"accessDeniedWithAuthModal":{"embedded":{"text":"Visit the Projects site for cool project ideas"},"heading":"You can\'t access this project","newProject":"Create a new code project","projectsSiteLinkText":"Explore Projects site","text":"Visit the Projects site for cool project ideas or start coding in a new project."},"loading":"Loading","notFoundModal":{"embedded":{"text":"Visit the Projects site for cool project ideas"},"heading":"This project does not exist","newProject":"Start new code project","projectsSiteLinkText":"Explore Projects site","text":"You can start coding in a new project, or visit the Projects site for cool project ideas."},"untitled":"Untitled project"},"projectHeader":{"subTitle":"Code Editor","title":"Your projects","text":"Select a project to continue coding, view, or edit it."},"projectList":{"delete":"Delete","deleteLabel":"Delete project","empty":"No projects created yet","label":"Open project menu","loading":"Loading","loadingFailed":"Failed to load projects","newProject":"Create a new project","pagination":{"first":"First page","last":"Last page","next":"Next page","previous":"Previous page","more":"Load more projects"},"rename":"Rename","renameLabel":"Rename project","renameProjectModal":{"cancel":"Do not save","heading":"Rename project","inputLabel":"Change the name of your project","save":"Save"},"updated":"Edited","python_type":"Python","html_type":"HTML"},"projectTypes":{"html":"HTML/CSS","python":"Python"},"runButton":{"run":"Run","stop":"Stop","stopping":"Stopping..."},"saveStatus":{"saving":"Saving","saved":"Saved"},"sidebar":{"collapse":"Collapse sidebar","download":"Download project","expand":"Expand sidebar","file":"Project files","images":"Image gallery","settings":"Settings","projects":"Projects","information":"Information","information_text":"Our Code Editor is a tool to help young people learn to code. We have only included functions that are simple and safe to use. That\'s why, for example, links to other websites are not allowed.","feedback":"Feedback","help":"Help","privacy":"Privacy","cookies":"Cookies","accessibility":"Accessibility","safeguarding":"Safeguarding","charity":"Raspberry Pi Foundation - UK registered charity 1129409","settingsMenu":{"heading":"Settings","textSize":"Text size","theme":"Theme","textSizeOptions":{"large":"Large","medium":"Medium","small":"Small"},"themeOptions":{"dark":"Dark","light":"Light"}}},"
|
|
8
|
+
module.exports = /*#__PURE__*/JSON.parse('{"modal":{"close":"Close","error":{"error":"Error","heading":"An error has occurred","externalLink":{"message":"Unfortunately links to external sites are not available in the Editor."}}},"editorPanel":{"ariaLabel":"editor text input. Press Escape then Tab to move focus out of the editor","characterLimitError":"Error: Character limit reached","characterLimitExplanation_one":"Files in the editor are limited to {{count}} character","characterLimitExplanation_other":"Files in the editor are limited to {{count}} characters","viewOnly":"View only","close":"close"},"filePanel":{"errors":{"reservedFileName":"{{fileName}} is a reserved file name. Please choose a different name.","containsSpaces":"File names must not contain spaces.","generalError":"Error","notUnique":"File names must be unique.","or":"or","unsupportedExtension":"File names must end in {{allowedExtensions}}."},"files":"Project files","images":"Image gallery","newFileButton":"Add file","newFileModal":{"cancel":"Cancel","heading":"Add a new file to your project","helpText":"Remember to add the file extension at the end of your file name, for example, {{examples}}","helpTextExample":{"html":"\'file.html\', \'file.css\' or \'file.js\'","python":"\'file.py\'"},"inputLabel":"Name your file","addFile":"Add file"},"renameFileModal":{"cancel":"Cancel","heading":"Rename file","inputLabel":"Name your file","save":"Save"},"fileMenu":{"label":"Open file menu","renameItem":"Rename file"}},"footer":{"accessibility":"Accessibility","charityNameAndNumber":"Raspberry Pi Foundation UK registered charity 1129409","cookies":"Cookies","privacy":"Privacy","safeguarding":"Safeguarding"},"projectName":{"label":"Project name","newProject":"New Project"},"header":{"download":"Download","editorLogoAltText":"Editor logo","projects":"Your projects","renameProject":"Edit project name","renameSave":"Save project name","save":"Save","loginToSave":"Log in to save","settings":"Settings"},"projectsPanel":{"projects":"Projects","yourProjectsButton":"Go to your projects","projectTypeLabel":"Project type"},"settingsPanel":{"info":"Settings"},"input":{"comment":{"py5":"Py5: imported mode"}},"mobile":{"code":"Code","menu":"Menu","output":"Output","preview":"Preview","steps":"Steps"},"modals":{"close":"Close"},"notifications":{"close":"close","loginPrompt":"To save this project and access it later, don\'t forget to log in or sign up!","projectRenamed":"You have renamed your project.","savePrompt":"Save this project to access it later under \\"Your projects\\"."},"project":{"accessDeniedWithAuthModal":{"embedded":{"text":"Visit the Projects site for cool project ideas"},"heading":"You can\'t access this project","newProject":"Create a new code project","projectsSiteLinkText":"Explore Projects site","text":"Visit the Projects site for cool project ideas or start coding in a new project."},"loading":"Loading","notFoundModal":{"embedded":{"text":"Visit the Projects site for cool project ideas"},"heading":"This project does not exist","newProject":"Start new code project","projectsSiteLinkText":"Explore Projects site","text":"You can start coding in a new project, or visit the Projects site for cool project ideas."},"untitled":"Untitled project"},"projectHeader":{"subTitle":"Code Editor","title":"Your projects","text":"Select a project to continue coding, view, or edit it."},"projectList":{"delete":"Delete","deleteLabel":"Delete project","empty":"No projects created yet","label":"Open project menu","loading":"Loading","loadingFailed":"Failed to load projects","newProject":"Create a new project","pagination":{"first":"First page","last":"Last page","next":"Next page","previous":"Previous page","more":"Load more projects"},"rename":"Rename","renameLabel":"Rename project","renameProjectModal":{"cancel":"Do not save","heading":"Rename project","inputLabel":"Change the name of your project","save":"Save"},"updated":"Edited","python_type":"Python","html_type":"HTML"},"projectTypes":{"html":"HTML/CSS","python":"Python"},"runButton":{"run":"Run","stop":"Stop","stopping":"Stopping..."},"saveStatus":{"saving":"Saving","saved":"Saved"},"sidebar":{"collapse":"Collapse sidebar","download":"Download project","expand":"Expand sidebar","file":"Project files","images":"Image gallery","settings":"Settings","projects":"Projects","information":"Information","information_text":"Our Code Editor is a tool to help young people learn to code. We have only included functions that are simple and safe to use. That\'s why, for example, links to other websites are not allowed.","feedback":"Feedback","help":"Help","privacy":"Privacy","cookies":"Cookies","accessibility":"Accessibility","safeguarding":"Safeguarding","charity":"Raspberry Pi Foundation - UK registered charity 1129409","settingsMenu":{"heading":"Settings","textSize":"Text size","theme":"Theme","textSizeOptions":{"large":"Large","medium":"Medium","small":"Small"},"themeOptions":{"dark":"Dark","light":"Light"}}},"newInputPanelButton":{"buttonText":"Add another panel"},"button":{"yes":"Yes","no":"No"},"common":{"or":"or"},"webComponent":{"failed":"Load failed"}}');
|
|
9
9
|
|
|
10
10
|
/***/ })
|
|
11
11
|
|
package/dist/assets/{PyodideWorkerb1409749c37aedc47dc8.js → PyodideWorkereba24d5259c22fbe422a.js}
RENAMED
|
@@ -133,6 +133,8 @@ const PyodideWorker = () => {
|
|
|
133
133
|
let useSyncXhrStdin = false;
|
|
134
134
|
/** True when current input request was cancelled from outside the worker. */
|
|
135
135
|
let stdinCancelled = false;
|
|
136
|
+
/** True when the sync-XHR stdin transport failed (SW unreachable / request escaped). */
|
|
137
|
+
let stdinTransportFailed = false;
|
|
136
138
|
/** Used when SharedArrayBuffer is unavailable: resolve for the pending input() call. */
|
|
137
139
|
let pendingStdinResolve = null;
|
|
138
140
|
// Until Pyodide is fully initialised, keep stdout/stderr in the dev console only.
|
|
@@ -185,6 +187,7 @@ const PyodideWorker = () => {
|
|
|
185
187
|
case "runPython":
|
|
186
188
|
currentRunId = data.runId || null;
|
|
187
189
|
stdinCancelled = false;
|
|
190
|
+
stdinTransportFailed = false;
|
|
188
191
|
runPython(data.python, data.userModuleNames);
|
|
189
192
|
break;
|
|
190
193
|
case "stopPython": {
|
|
@@ -238,10 +241,7 @@ const PyodideWorker = () => {
|
|
|
238
241
|
await runUserCode();
|
|
239
242
|
});
|
|
240
243
|
} catch (error) {
|
|
241
|
-
const isStdinControlError =
|
|
242
|
-
error?.message === "PYODIDE_STDIN_CANCELLED" ||
|
|
243
|
-
error?.message === "PYODIDE_STDIN_TIMEOUT" ||
|
|
244
|
-
error?.message === "PYODIDE_STDIN_ABORTED";
|
|
244
|
+
const isStdinControlError = error?.message === "PYODIDE_STDIN_CANCELLED";
|
|
245
245
|
|
|
246
246
|
if (stdinCancelled || isStdinControlError) {
|
|
247
247
|
postMessage({
|
|
@@ -256,6 +256,19 @@ const PyodideWorker = () => {
|
|
|
256
256
|
return;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
if (stdinTransportFailed) {
|
|
260
|
+
postMessage({
|
|
261
|
+
method: "handleError",
|
|
262
|
+
file: "main.py",
|
|
263
|
+
line: "",
|
|
264
|
+
mistake: "",
|
|
265
|
+
type: "InputUnavailableError",
|
|
266
|
+
info: "Input timed out. Rerun your code and try again.",
|
|
267
|
+
});
|
|
268
|
+
await clearPyodideData(userModuleNames);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
259
272
|
if (!(error instanceof pyodide.ffi.PythonError)) {
|
|
260
273
|
postMessage({
|
|
261
274
|
method: "handleError",
|
|
@@ -691,59 +704,64 @@ const PyodideWorker = () => {
|
|
|
691
704
|
};
|
|
692
705
|
|
|
693
706
|
const readInputViaSyncXhr = (runId) => {
|
|
694
|
-
|
|
695
|
-
const requestUrl = new URL(
|
|
696
|
-
stdinFallbackConfig.endpointPath,
|
|
697
|
-
// eslint-disable-next-line no-restricted-globals
|
|
698
|
-
self.location.origin,
|
|
699
|
-
);
|
|
700
|
-
requestUrl.searchParams.set("runId", runId || "");
|
|
701
|
-
requestUrl.searchParams.set("requestId", requestId);
|
|
702
|
-
requestUrl.searchParams.set("clientId", stdinFallbackConfig.clientId);
|
|
703
|
-
|
|
704
|
-
const xhr = new XMLHttpRequest();
|
|
705
|
-
xhr.open("GET", requestUrl.toString(), false);
|
|
706
|
-
xhr.setRequestHeader("X-Pyodide-Stdin-Request", "true");
|
|
707
|
-
xhr.setRequestHeader("Cache-Control", "no-store");
|
|
708
|
-
|
|
707
|
+
// Mark any failure with XHR setup as stdinTransportFailed for a clean error
|
|
709
708
|
try {
|
|
709
|
+
const requestId = crypto.randomUUID();
|
|
710
|
+
const requestUrl = new URL(
|
|
711
|
+
stdinFallbackConfig.endpointPath,
|
|
712
|
+
// eslint-disable-next-line no-restricted-globals
|
|
713
|
+
self.location.origin,
|
|
714
|
+
);
|
|
715
|
+
requestUrl.searchParams.set("runId", runId || "");
|
|
716
|
+
requestUrl.searchParams.set("requestId", requestId);
|
|
717
|
+
requestUrl.searchParams.set("clientId", stdinFallbackConfig.clientId);
|
|
718
|
+
|
|
719
|
+
const xhr = new XMLHttpRequest();
|
|
720
|
+
xhr.open("GET", requestUrl.toString(), false);
|
|
721
|
+
xhr.setRequestHeader("X-Pyodide-Stdin-Request", "true");
|
|
722
|
+
xhr.setRequestHeader("Cache-Control", "no-store");
|
|
710
723
|
xhr.send(null);
|
|
711
|
-
} catch (_) {
|
|
712
|
-
throw new Error("Failed to read Python input via stdin fallback");
|
|
713
|
-
}
|
|
714
724
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
725
|
+
if (xhr.status === STDIN_CANCELLED_RESPONSE_CODE) {
|
|
726
|
+
stdinCancelled = true;
|
|
727
|
+
throw new Error("PYODIDE_STDIN_CANCELLED");
|
|
728
|
+
}
|
|
719
729
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
730
|
+
if (xhr.status === STDIN_TIMEOUT_RESPONSE_CODE) {
|
|
731
|
+
stdinTransportFailed = true;
|
|
732
|
+
throw new Error("PYODIDE_STDIN_TIMEOUT");
|
|
733
|
+
}
|
|
723
734
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
735
|
+
if (xhr.status === STDIN_ABORTED_RESPONSE_CODE) {
|
|
736
|
+
stdinTransportFailed = true;
|
|
737
|
+
throw new Error("PYODIDE_STDIN_ABORTED");
|
|
738
|
+
}
|
|
727
739
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
740
|
+
// EOF: orchestrator signalled Ctrl+D / no more input. Returning null tells
|
|
741
|
+
// Pyodide's line-based stdin handler to raise EOFError at the input() call
|
|
742
|
+
// site, rather than escalating to a KeyboardInterrupt that tears down the run.
|
|
743
|
+
if (xhr.status === STDIN_EOF_RESPONSE_CODE) {
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
734
746
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
747
|
+
if (xhr.status !== 200) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
`Python input request failed with status ${xhr.status}: ${xhr.responseText}`,
|
|
750
|
+
);
|
|
751
|
+
}
|
|
740
752
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
753
|
+
let content = xhr.responseText ?? "";
|
|
754
|
+
if (!content.endsWith("\n")) {
|
|
755
|
+
content += "\n";
|
|
756
|
+
}
|
|
745
757
|
|
|
746
|
-
|
|
758
|
+
return content;
|
|
759
|
+
} catch (err) {
|
|
760
|
+
if (!stdinCancelled) {
|
|
761
|
+
stdinTransportFailed = true;
|
|
762
|
+
}
|
|
763
|
+
throw err;
|
|
764
|
+
}
|
|
747
765
|
};
|
|
748
766
|
|
|
749
767
|
const clearPyodideData = async (userModuleNames) => {
|
|
@@ -175,6 +175,13 @@ self.addEventListener("message", (event) => {
|
|
|
175
175
|
event.source && "id" in event.source ? event.source.id : "";
|
|
176
176
|
const clientId = data.clientId || eventClientId || "";
|
|
177
177
|
|
|
178
|
+
if (data.type === "PYODIDE_STDIN_PROBE") {
|
|
179
|
+
if (event.source) {
|
|
180
|
+
event.source.postMessage({ type: "PYODIDE_STDIN_PROBE_ACK" });
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
178
185
|
if (data.type === "PYODIDE_STDIN_REGISTER") {
|
|
179
186
|
if (data.clientId && eventClientId) {
|
|
180
187
|
tabClientToWindowClient.set(String(data.clientId), String(eventClientId));
|