@wasm-oj/organizer 0.2.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/LICENSE +21 -0
- package/README.md +5 -0
- package/THIRD_PARTY_NOTICES.md +302 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +546 -0
- package/licenses/fflate-MIT.txt +21 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JacobLinCool
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# `@wasm-oj/organizer`
|
|
2
|
+
|
|
3
|
+
Schemas and publication utilities for problem collections, contest projections, and immutable
|
|
4
|
+
judge packages. The Organizer boundary validates schema, paths, size, digest, and deployability; it
|
|
5
|
+
does not compile, run, score, or assess a reference solution.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
This file covers third-party software distributed by the WASM-OJ code and
|
|
4
|
+
toolchain packages, including the content-addressed compiler and runtime assets
|
|
5
|
+
mirrored under `public/toolchains`. The WASM-OJ source code is licensed under the MIT License in
|
|
6
|
+
`LICENSE`; that license does not replace the terms of any component listed
|
|
7
|
+
below.
|
|
8
|
+
|
|
9
|
+
The canonical machine-readable component, distribution, source-revision, and
|
|
10
|
+
license-file inventory is `licenses/components.json`. Its verifier binds every
|
|
11
|
+
listed file to a SHA-256 digest and rejects unlisted toolchain or license files.
|
|
12
|
+
|
|
13
|
+
## JavaScript runtime dependency
|
|
14
|
+
|
|
15
|
+
### N-API keyring
|
|
16
|
+
|
|
17
|
+
- Component: `@napi-rs/keyring` 1.3.0
|
|
18
|
+
- Source revision: `Brooooooklyn/keyring-node@e46be75c3ba8d5fde6b88a17c6153b87ffe4b946`
|
|
19
|
+
- Source: <https://github.com/Brooooooklyn/keyring-node/tree/e46be75c3ba8d5fde6b88a17c6153b87ffe4b946>
|
|
20
|
+
- License: MIT
|
|
21
|
+
- License material: `licenses/napi-rs-keyring-MIT.txt`
|
|
22
|
+
|
|
23
|
+
The `woj` CLI stores remote access tokens in the operating system credential
|
|
24
|
+
store through this native binding. It does not provide a plaintext fallback.
|
|
25
|
+
|
|
26
|
+
### es-module-lexer
|
|
27
|
+
|
|
28
|
+
- Component: `es-module-lexer` 2.3.1
|
|
29
|
+
- npm integrity: `sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==`
|
|
30
|
+
- Source revision: `guybedford/es-module-lexer@2b2e6209bac5c06c6ba457f9730014613e1128fb`
|
|
31
|
+
- Source: <https://github.com/guybedford/es-module-lexer/tree/2b2e6209bac5c06c6ba457f9730014613e1128fb>
|
|
32
|
+
- License: MIT
|
|
33
|
+
- License material: `licenses/es-module-lexer-MIT.txt`
|
|
34
|
+
|
|
35
|
+
WASM-OJ uses es-module-lexer inside the browser runner Worker to reject every
|
|
36
|
+
static or dynamic transitive import before loading a content-pinned runtime
|
|
37
|
+
driver plug-in.
|
|
38
|
+
|
|
39
|
+
### fflate
|
|
40
|
+
|
|
41
|
+
- Component: `fflate` 0.8.3
|
|
42
|
+
- Source revision: `101arrowz/fflate@v0.8.3`
|
|
43
|
+
- Source: <https://github.com/101arrowz/fflate/tree/v0.8.3>
|
|
44
|
+
- License: MIT
|
|
45
|
+
- License material: `licenses/fflate-MIT.txt`
|
|
46
|
+
|
|
47
|
+
WASM-OJ uses fflate for bounded dependency archive extraction and to verify the
|
|
48
|
+
canonical Go module `h1:` hash over module ZIP entries.
|
|
49
|
+
|
|
50
|
+
### Wasmer JavaScript SDK
|
|
51
|
+
|
|
52
|
+
- Component: `@wasmer/sdk` 0.10.0
|
|
53
|
+
- npm integrity: `sha512-YQ+s5tGag6P/I8kp9BTH+XhjoS9UFvWiZJvnWEEovClHffhYToKhprWr4UJG7wLP7c/2HQpGkF7ZrjoUvKjdmA==`
|
|
54
|
+
- Source revision: `wasmerio/wasmer-js@93b8b738ebd3ee57e118da0f0eb795b97d5b999e`
|
|
55
|
+
- Locked Rust graph: `Cargo.lock` SHA-256
|
|
56
|
+
`d352926f3f05e3d4308c4e261711d07db568e5c2b4387067180f920da074791f`
|
|
57
|
+
- Source: <https://github.com/wasmerio/wasmer-js/tree/93b8b738ebd3ee57e118da0f0eb795b97d5b999e>
|
|
58
|
+
- License: MIT
|
|
59
|
+
- License material: `licenses/wasmer-sdk-MIT.txt`,
|
|
60
|
+
`licenses/wasmer-sdk-dependencies.html`, and
|
|
61
|
+
`licenses/wasmer-sdk-dependencies.json`
|
|
62
|
+
|
|
63
|
+
WASM-OJ consumes the official npm artifact without patching the installed
|
|
64
|
+
package. The generated dependency report covers every package in the pinned
|
|
65
|
+
normal Cargo dependency graph selected for `wasm32-unknown-unknown`; its compact
|
|
66
|
+
inventory binds all 332 package identities and the exact HTML report digest.
|
|
67
|
+
|
|
68
|
+
## Distributed toolchain assets
|
|
69
|
+
|
|
70
|
+
### TypeScript-Go
|
|
71
|
+
|
|
72
|
+
- Distributed asset: `typescript-7.0.2.wasm.gz.bin`
|
|
73
|
+
- Compressed SHA-256: `06e58ce887d95d1895055699b8dc96a1cde7d1f2baa48de40f9b790e3271dc16`
|
|
74
|
+
- Expanded Wasm SHA-256: `27c931087b3b0cc48804f3749b07eb710538c4ab175969db8a8ff680e6ec4fde`
|
|
75
|
+
- Source revision: `microsoft/typescript-go@2bd066d87f5bafd315be9f40889d0a60b9e58e0b`
|
|
76
|
+
- Source: <https://github.com/microsoft/typescript-go/tree/2bd066d87f5bafd315be9f40889d0a60b9e58e0b>
|
|
77
|
+
- Build runtime: Go 1.26.3 standard library (`GOOS=wasip1`, `GOARCH=wasm`)
|
|
78
|
+
- Licenses: TypeScript-Go Apache-2.0; Go standard library BSD-3-Clause
|
|
79
|
+
- License and attribution material: `licenses/Apache-2.0.txt`,
|
|
80
|
+
`licenses/typescript-go-NOTICE.txt`, and
|
|
81
|
+
`licenses/go-BSD-3-Clause.txt`
|
|
82
|
+
|
|
83
|
+
The TypeScript-Go notice is carried from the exact pinned source revision
|
|
84
|
+
(normalized from CRLF to LF without changing its text). The Go license is from
|
|
85
|
+
the exact `go1.26.3` source tag used by the reproducible build script.
|
|
86
|
+
|
|
87
|
+
### Go compiler toolchain
|
|
88
|
+
|
|
89
|
+
- Distributed assets: `go-1.26.5-wasip1.webc.gz.bin`,
|
|
90
|
+
`go-1.26.5-wasip1.stdlib.gz.bin`, and
|
|
91
|
+
`go-1.26.5-wasip1.manifest.json`
|
|
92
|
+
- Source: <https://go.dev/dl/go1.26.5.src.tar.gz>
|
|
93
|
+
- Source archive SHA-256:
|
|
94
|
+
`495be4bc87176ac567392e5b4116abd98466d33d7b49d41e764ccc6976b2dc42`
|
|
95
|
+
- License: BSD-3-Clause
|
|
96
|
+
- License material: `licenses/go-BSD-3-Clause.txt`
|
|
97
|
+
|
|
98
|
+
WASM-OJ packages the standard Go `compile` and `link` commands plus the matching
|
|
99
|
+
349-package `GOOS=wasip1 GOARCH=wasm` standard library. The stored license is
|
|
100
|
+
byte-identical to `go/LICENSE` in the exact Go 1.26.5 source distribution.
|
|
101
|
+
|
|
102
|
+
### Java compiler toolchain
|
|
103
|
+
|
|
104
|
+
- Distributed assets: `java-teavm-0.13.1.wasi.compiler.webc.gz.bin`,
|
|
105
|
+
`java-teavm-0.13.1.compile-classlib.bin`, and
|
|
106
|
+
`java-teavm-0.13.1.runtime-classlib.bin`
|
|
107
|
+
- TeaVM Java compiler source: <https://github.com/konsoletyper/teavm-javac/tree/7e4a44cf521694a4e326e33850dd8aec165eb5c9>
|
|
108
|
+
- TeaVM core source: `konsoletyper/teavm@b3a245b7d9034ff35cdfab2def057a3d4f256efb`
|
|
109
|
+
plus the pinned WASI compiler and math-intrinsic patch
|
|
110
|
+
- Compiler package compressed SHA-256:
|
|
111
|
+
`129f1f51d591e58954f88787d36396b856a9a68ba3ae9c9d14f20bd67c2c7722`
|
|
112
|
+
- Compiler package expanded SHA-256:
|
|
113
|
+
`f8f86761cf31062565187e4a66f73b6903f257fe84c0ce70ea1cd28441b6c2e9`
|
|
114
|
+
- Licenses: TeaVM Apache-2.0; OpenJDK class library GPL-2.0 with Classpath
|
|
115
|
+
Exception
|
|
116
|
+
- License material: `licenses/Apache-2.0.txt` and
|
|
117
|
+
`licenses/openjdk-21-GPL-2.0-with-Classpath-exception.txt`
|
|
118
|
+
|
|
119
|
+
The compiler emits standalone `wasip1` modules. The class-library archives are
|
|
120
|
+
mounted only during compilation and are not included in submitted artifacts.
|
|
121
|
+
|
|
122
|
+
### QuickJS-ng
|
|
123
|
+
|
|
124
|
+
- Distributed asset: `quickjs-0.15.1.wasm.gz.bin`
|
|
125
|
+
- Compressed SHA-256: `8c7f0588210490e7d77f198fc91f72c1b94787ab4c359c4786ca59a363c4f5e8`
|
|
126
|
+
- Expanded Wasm SHA-256: `956bf2b3700690e1817034eb8e063cfd9781c66b4bffa244ef4f9445656ccfa1`
|
|
127
|
+
- Source revision: `quickjs-ng/quickjs@fd0a0210b7be00957751871e7e01b8291268fc29`
|
|
128
|
+
- Source: <https://github.com/quickjs-ng/quickjs/tree/fd0a0210b7be00957751871e7e01b8291268fc29>
|
|
129
|
+
- Build SDK: [WASI SDK 24.0 release archive for arm64 macOS](https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-arm64-macos.tar.gz),
|
|
130
|
+
SHA-256 `aeae999396d5f5caa5ce419f52e83c35869d5fd21d40af80acba2c80f51b0b3a`
|
|
131
|
+
- WASI SDK source revision: `WebAssembly/wasi-sdk@d2bea01edcc46f731156a817f710cdd9fc9c1c19`
|
|
132
|
+
- LLVM and compiler-rt source revision: `llvm/llvm-project@26a1d6601d727a96f4301d0d8647b5a42760ae0c`
|
|
133
|
+
- WASI libc source revision: `WebAssembly/wasi-libc@b9ef79d7dbd47c6c5bafdae760823467c2f60b70`
|
|
134
|
+
- Sources: <https://github.com/WebAssembly/wasi-sdk/tree/d2bea01edcc46f731156a817f710cdd9fc9c1c19>,
|
|
135
|
+
<https://github.com/llvm/llvm-project/tree/26a1d6601d727a96f4301d0d8647b5a42760ae0c>, and
|
|
136
|
+
<https://github.com/WebAssembly/wasi-libc/tree/b9ef79d7dbd47c6c5bafdae760823467c2f60b70>
|
|
137
|
+
- Licenses: QuickJS-ng and the WASM-OJ adapter MIT; LLVM compiler-rt
|
|
138
|
+
Apache-2.0 WITH LLVM-exception plus the third-party terms collected in its
|
|
139
|
+
license; WASI libc Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT, with
|
|
140
|
+
separately licensed dlmalloc, cloudlibc-derived, and musl-derived portions
|
|
141
|
+
- License and attribution material: `licenses/quickjs-ng-MIT.txt`,
|
|
142
|
+
`licenses/wasi-sdk-24.0-Apache-2.0-LLVM-exception.txt`,
|
|
143
|
+
`licenses/wasi-sdk-24.0-compiler-rt-LICENSE.txt`,
|
|
144
|
+
`licenses/wasi-libc-b9ef79d-LICENSE.txt`,
|
|
145
|
+
`licenses/wasi-libc-b9ef79d-Apache-2.0.txt`,
|
|
146
|
+
`licenses/wasi-libc-b9ef79d-MIT.txt`,
|
|
147
|
+
`licenses/wasi-libc-b9ef79d-cloudlibc-BSD-2-Clause.txt`,
|
|
148
|
+
`licenses/wasi-libc-b9ef79d-dlmalloc-CC0-NOTICE.txt`, and
|
|
149
|
+
`licenses/wasi-libc-b9ef79d-emmalloc-NOTICE.txt`, and
|
|
150
|
+
`licenses/wasi-libc-b9ef79d-musl-MIT.txt`
|
|
151
|
+
|
|
152
|
+
The reproducible build links `crt1-command.o`, `libm.a`,
|
|
153
|
+
`libwasi-emulated-signal.a`, `libc.a`, and
|
|
154
|
+
`libclang_rt.builtins-wasm32.a` from that one digest-verified SDK archive.
|
|
155
|
+
Linker tracing confirms that the selected allocator is dlmalloc, not emmalloc.
|
|
156
|
+
WASM-OJ distributes the resulting stripped Wasm runtime, not the host Clang, LLD,
|
|
157
|
+
or strip executables. The build script nevertheless verifies those executables
|
|
158
|
+
and every linked archive by SHA-256 before compiling.
|
|
159
|
+
|
|
160
|
+
### Rust compiler and standard libraries
|
|
161
|
+
|
|
162
|
+
- Distributed assets: `rust-1.91.1-dev.webc.gz.bin` and
|
|
163
|
+
`rust-1.91.1-dev.manifest.json`
|
|
164
|
+
- Build source: `olimpiadi-informatica/wasm-compilers@ae62cab6adf0665377d19ffa39daeaf758290431`
|
|
165
|
+
- Build artifact: GitHub Actions run `26545267884`, `rust.tar.br` SHA-256
|
|
166
|
+
`ba0096d05275954d852a3fb3a9c4c9438dad501f8e428b867c0b88cfa7301c14`
|
|
167
|
+
- Linker payload: `@yowasp/clang@22.0.0-git20542-10`, npm archive SHA-256
|
|
168
|
+
`6230ea1afa9691fa065935cf68c01642ff9b31c183fe8ac64cdfda025df06009`;
|
|
169
|
+
its exact source revisions and license closure are recorded in the following
|
|
170
|
+
“Clang, LLD, libc++, and WASI libc” section
|
|
171
|
+
- Rust source submodule: `rust-lang/rust@ed61e7d7e242494fb7057f2657300d9e77bb4fcb`
|
|
172
|
+
- LLVM source submodule: `llvm/llvm-project@87f0227cb60147a26a1eeb4fb06e3b505e9c7261`
|
|
173
|
+
- GCC source submodule: `gcc-mirror/gcc@ae91b5dd14920ff9671db8ff80c0d763d25f977f`
|
|
174
|
+
- Source: <https://github.com/olimpiadi-informatica/wasm-compilers/tree/ae62cab6adf0665377d19ffa39daeaf758290431>
|
|
175
|
+
- Build repository license: Apache-2.0
|
|
176
|
+
- Rust license: MIT OR Apache-2.0; LLVM license: Apache-2.0 WITH LLVM-exception
|
|
177
|
+
- `libloading` 0.8.8: ISC; Cargo.lock checksum
|
|
178
|
+
`07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667`
|
|
179
|
+
- GCC libstdc++ runtime components: GPL-3.0 with GCC Runtime Library
|
|
180
|
+
Exception 3.1
|
|
181
|
+
- License and attribution texts: `licenses/rust-MIT.txt`,
|
|
182
|
+
`licenses/Apache-2.0.txt`, `licenses/LLVM-exception.txt`,
|
|
183
|
+
`licenses/rust-COPYRIGHT.txt`, `licenses/rust-COPYRIGHT.html`,
|
|
184
|
+
`licenses/rust-COPYRIGHT-library.html`, `licenses/libloading-ISC.txt`,
|
|
185
|
+
`licenses/GPL-3.0.txt`, and
|
|
186
|
+
`licenses/GCC-Runtime-Library-Exception-3.1.txt`
|
|
187
|
+
|
|
188
|
+
The pinned build repository contains its complete Rust/LLVM source revisions,
|
|
189
|
+
the applied `rust.patch`, and the GitHub workflow used for the pinned artifact.
|
|
190
|
+
That workflow builds libstdc++ from the pinned GCC submodule before building
|
|
191
|
+
LLVM and rustc. The custom `rust.tar.br` omits Rust's generated binary-release
|
|
192
|
+
notices, so WASM-OJ carries the complete `COPYRIGHT.html` and
|
|
193
|
+
`COPYRIGHT-library.html` from the official Rust 1.91.1 release for the exact
|
|
194
|
+
`ed61e7d7e` source revision. The official release archive used to source those
|
|
195
|
+
notices is `rustc-1.91.1-x86_64-unknown-linux-gnu.tar.xz`, SHA-256
|
|
196
|
+
`4b4c596fc5268435c310a79c2e231a5a3567572c930ac0740ef9e147e83baf4e`.
|
|
197
|
+
|
|
198
|
+
WASM-OJ copies the matching `wasm32-wasip1-threads` sysroot, replaces rustc's
|
|
199
|
+
WASI `random_get` and `clock_time_get` imports with deterministic internal
|
|
200
|
+
implementations, and packages the transformed atom and sysroot together with
|
|
201
|
+
the verified YoWASP LLVM linker atom and resources into one WebC. The provenance
|
|
202
|
+
manifest records source-rustc, transformed-rustc, linker archive, linker core,
|
|
203
|
+
and linker-resource SHA-256 digests. These transformations and the generated
|
|
204
|
+
WebC do not relicense Rust or any third-party component identified by the exact
|
|
205
|
+
Rust COPYRIGHT reports and YoWASP source closure carried here.
|
|
206
|
+
|
|
207
|
+
### Clang, LLD, libc++, PCH, and WASI libc
|
|
208
|
+
|
|
209
|
+
- Distributed assets: `clang-22.0.0-git20542-10.webc.gz.bin`, the matching
|
|
210
|
+
debug/release libc++ PCH payloads, and their admission manifest
|
|
211
|
+
- Clang source artifact: `@yowasp/clang@22.0.0-git20542-10`, npm archive
|
|
212
|
+
SHA-256 `6230ea1afa9691fa065935cf68c01642ff9b31c183fe8ac64cdfda025df06009`
|
|
213
|
+
- YoWASP source revision: `YoWASP/clang@944dd7c774954180e621cc8e12984023a7f8bcbe`
|
|
214
|
+
- LLVM source revision: `YoWASP/llvm-project@97196c8eeb1d495fa43bb8af2fb26af5ef5b89fb`
|
|
215
|
+
- WASI libc source revision: `WebAssembly/wasi-libc@ac020b86fd44bafe60aa4fa12f407d16e3731329`
|
|
216
|
+
- Sources: <https://github.com/YoWASP/clang/tree/944dd7c774954180e621cc8e12984023a7f8bcbe>
|
|
217
|
+
and <https://github.com/YoWASP/llvm-project/tree/97196c8eeb1d495fa43bb8af2fb26af5ef5b89fb>
|
|
218
|
+
- LLVM license: Apache-2.0 WITH LLVM-exception
|
|
219
|
+
- WASI libc license: Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT,
|
|
220
|
+
with separately licensed portions identified by the upstream notice
|
|
221
|
+
- License and attribution texts: `licenses/LLVM-exception.txt`,
|
|
222
|
+
`licenses/Apache-2.0.txt`, `licenses/wasi-libc-ac020b86-LICENSE.txt`,
|
|
223
|
+
`licenses/wasi-libc-ac020b86-musl-fts-BSD-3-Clause.txt`,
|
|
224
|
+
`licenses/wasi-sdk-24.0-Apache-2.0-LLVM-exception.txt`,
|
|
225
|
+
`licenses/wasi-libc-b9ef79d-Apache-2.0.txt`,
|
|
226
|
+
`licenses/wasi-libc-b9ef79d-MIT.txt`,
|
|
227
|
+
`licenses/wasi-libc-b9ef79d-cloudlibc-BSD-2-Clause.txt`,
|
|
228
|
+
`licenses/wasi-libc-b9ef79d-dlmalloc-CC0-NOTICE.txt`,
|
|
229
|
+
`licenses/wasi-libc-b9ef79d-emmalloc-NOTICE.txt`, and
|
|
230
|
+
`licenses/wasi-libc-b9ef79d-musl-MIT.txt`
|
|
231
|
+
|
|
232
|
+
The published npm manifest incorrectly says `ISC` and omits a license file.
|
|
233
|
+
The exact source revision's root `LICENSE.txt` and npm README both explicitly
|
|
234
|
+
license the package under Apache-2.0. WASM-OJ does not redistribute the npm
|
|
235
|
+
JavaScript wrapper: the packaging script digest-verifies and extracts only the
|
|
236
|
+
LLVM core and LLVM/WASI resource payload, whose complete upstream license terms
|
|
237
|
+
are carried above. The common Apache, MIT, cloudlibc, dlmalloc, emmalloc, and
|
|
238
|
+
musl terms are byte-identical between the pinned `ac020b86` and `b9ef79d7`
|
|
239
|
+
source trees, so the inventory reuses the exact digest-bound `b9ef79d7` copies;
|
|
240
|
+
the revision-specific root and musl-fts notices remain separate. The generated
|
|
241
|
+
WebC and pinned command manifests do not relicense those payloads.
|
|
242
|
+
|
|
243
|
+
### CPython 3.14.6 for WASI P1
|
|
244
|
+
|
|
245
|
+
- Distributed assets: `python-3.14.6-wasip1.webc.gz.bin` and its provenance
|
|
246
|
+
manifest `python-3.14.6-wasip1.manifest.json`
|
|
247
|
+
- Official source archive: <https://www.python.org/ftp/python/3.14.6/Python-3.14.6.tar.xz>,
|
|
248
|
+
SHA-256 `143b1dddefaec3bd2e21e3b839b34a2b7fb9842272883c576420d605e9f30c63`
|
|
249
|
+
- Official SPDX document: <https://www.python.org/ftp/python/3.14.6/Python-3.14.6.tar.xz.spdx.json>,
|
|
250
|
+
SHA-256 `1f5d394856783fa77e1f1db280f84eabf693bffc1fb06a747f7116de9f99f3bd`
|
|
251
|
+
- Expanded WebC SHA-256:
|
|
252
|
+
`454ffc53936aa13a0d7f4afbb5bd50ada339c8ebd04bbf27ea19e4104cf43207`
|
|
253
|
+
- Distributed gzip SHA-256:
|
|
254
|
+
`218cd20ac4abb443e0700816010a615a345a43eae623a0232da2227135a6c7a6`
|
|
255
|
+
- Provenance manifest SHA-256:
|
|
256
|
+
`054eccad04a7cee7ba1661062142ef0d639976850981eab8fc785f48eb26129e`
|
|
257
|
+
- Deterministically exported `WOJFS002` runtime archive SHA-256:
|
|
258
|
+
`44d894f91487f20c2bb04fe496a9343db37d8720fb706472c2b4a7f3300db039`
|
|
259
|
+
- CPython license: Python Software Foundation License Version 2 and the
|
|
260
|
+
historical notices reproduced with it
|
|
261
|
+
- License text: `licenses/cpython-3.14.6-PSF-2.0.txt`
|
|
262
|
+
- Bundled third-party source: Expat 2.8.1 (MIT), HACL* revision
|
|
263
|
+
`8ba599b2f6c9701b3dc961db895b0856a2210f76` (MIT), and libmpdec 2.5.1
|
|
264
|
+
(BSD-2-Clause); their exact notices are
|
|
265
|
+
`licenses/cpython-expat-2.8.1-MIT.txt`,
|
|
266
|
+
`licenses/cpython-hacl-star-8ba599b-MIT.txt`, and
|
|
267
|
+
`licenses/cpython-libmpdec-2.5.1-BSD-2-Clause.txt`.
|
|
268
|
+
- Build toolchain: WASI SDK 24.0 revision
|
|
269
|
+
`d2bea01edcc46f731156a817f710cdd9fc9c1c19`, LLVM revision
|
|
270
|
+
`26a1d6601d727a96f4301d0d8647b5a42760ae0c`, and wasi-libc revision
|
|
271
|
+
`b9ef79d7dbd47c6c5bafdae760823467c2f60b70`.
|
|
272
|
+
|
|
273
|
+
WASM-OJ compiles this package from the pinned official sources, disables
|
|
274
|
+
`_socket`, removes test/development-only standard-library roots, canonicalizes
|
|
275
|
+
build-only sysconfig paths, and packages the complete source SPDX document and
|
|
276
|
+
applicable CPython, third-party, compiler-rt, WASI SDK, and wasi-libc notices
|
|
277
|
+
inside the WebC. The build does not consume or redistribute the retired Wasmer
|
|
278
|
+
Registry CPython/WASIX package, and WASM-OJ's MIT license does not apply to these
|
|
279
|
+
third-party contents.
|
|
280
|
+
|
|
281
|
+
## WASM-OJ runtime-core dependency closure
|
|
282
|
+
|
|
283
|
+
The browser runner embeds `wasm-oj-runtime-core` and its locked normal
|
|
284
|
+
dependency graph for the `wasm32-unknown-unknown` target with the `web` feature.
|
|
285
|
+
The complete generated license report, including the selected license text for
|
|
286
|
+
every dependency, is `licenses/runtime-core-dependencies.html`. Its compact,
|
|
287
|
+
machine-verifiable package inventory and report digest are recorded in
|
|
288
|
+
`licenses/runtime-core-dependencies.json`. Both files are generated with pinned
|
|
289
|
+
`cargo-about` 0.9.1 and are verified against `crates/runtime-core/Cargo.lock`.
|
|
290
|
+
|
|
291
|
+
## Vendored Rust crates
|
|
292
|
+
|
|
293
|
+
WASM-OJ's source tree also contains patched source copies used to build the
|
|
294
|
+
runtime and packaging tools:
|
|
295
|
+
|
|
296
|
+
- `vendor/shared-buffer`: MIT OR Apache-2.0; see
|
|
297
|
+
`vendor/shared-buffer/LICENSE_MIT.md` and
|
|
298
|
+
`vendor/shared-buffer/LICENSE_APACHE.md`.
|
|
299
|
+
- `vendor/virtual-fs`: MIT; see `vendor/virtual-fs/LICENSE`.
|
|
300
|
+
|
|
301
|
+
These vendored sources are build inputs and are not included in the npm package
|
|
302
|
+
allowlist.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { BROWSER_COLLECTION_SCHEMA, BROWSER_PROBLEM_SCHEMA, MANAGED_COLLECTION_SCHEMA, parseManagedCollectionContract, parseManagedCollectionSource, parseManagedCollectionV2, parseManagedCollectionValueV2, parseProblemCollectionIndex, parseStandaloneProblemBundle, problemCollectionRevision, verifyProblemBundleBytes, verifyProblemCollectionRevision } from '@wasm-oj/core';
|
|
2
|
+
|
|
3
|
+
declare function runCollectionCli(arguments_: readonly string[]): Promise<void>;
|
|
4
|
+
|
|
5
|
+
export { runCollectionCli };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat, mkdir, open, readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import { BROWSER_COLLECTION_SCHEMA, BROWSER_COLLECTION_SCHEMA as BROWSER_COLLECTION_SCHEMA$1, BROWSER_PROBLEM_SCHEMA, BROWSER_PROBLEM_SCHEMA as BROWSER_PROBLEM_SCHEMA$1, MANAGED_COLLECTION_SCHEMA, MANAGED_COLLECTION_SCHEMA as MANAGED_COLLECTION_SCHEMA$1, assertJudgeDataMatchesPracticePublic, canonicalJsonBytes, contestPublicProjectionBytes, deriveJudgeData, derivePracticePublic, encodeJudgePackage, parseManagedCollectionContract, parseManagedCollectionSource, parseManagedCollectionSource as parseManagedCollectionSource$1, parseManagedCollectionV2, parseManagedCollectionV2 as parseManagedCollectionV2$1, parseManagedCollectionValueV2, parseProblemCollectionIndex, parseProblemCollectionIndex as parseProblemCollectionIndex$1, parseStandaloneProblemBundle, parseStandaloneProblemBundle as parseStandaloneProblemBundle$1, problemCollectionRevision, problemCollectionRevision as problemCollectionRevision$1, validateJudgePackage, verifyProblemBundleBytes, verifyProblemBundleBytes as verifyProblemBundleBytes$1, verifyProblemCollectionRevision, verifyProblemCollectionRevision as verifyProblemCollectionRevision$1 } from "@wasm-oj/core";
|
|
7
|
+
//#region src/path-safety.ts
|
|
8
|
+
/** Resolve only the operating system's own temporary-directory alias. */
|
|
9
|
+
async function canonicalizeSystemTemporaryPrefix(value) {
|
|
10
|
+
const absolute = path.resolve(value);
|
|
11
|
+
const temporary = path.resolve(os.tmpdir());
|
|
12
|
+
if (absolute !== temporary && !absolute.startsWith(`${temporary}${path.sep}`)) return absolute;
|
|
13
|
+
const canonicalTemporary = await realpath(temporary);
|
|
14
|
+
return path.join(canonicalTemporary, path.relative(temporary, absolute));
|
|
15
|
+
}
|
|
16
|
+
/** True only when the absolute path's existing components do not traverse links. */
|
|
17
|
+
async function anchoredPathHasNoSymlink(value) {
|
|
18
|
+
const anchored = await canonicalizeSystemTemporaryPrefix(path.resolve(value));
|
|
19
|
+
const root = path.parse(anchored).root;
|
|
20
|
+
let current = root;
|
|
21
|
+
for (const segment of path.relative(root, anchored).split(path.sep).filter(Boolean)) {
|
|
22
|
+
current = path.join(current, segment);
|
|
23
|
+
try {
|
|
24
|
+
if ((await lstat(current)).isSymbolicLink()) return false;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.code === "ENOENT") return true;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/collection-cli.ts
|
|
34
|
+
var SOURCE_SCHEMA = "wasm-oj-browser-collection-source-v1";
|
|
35
|
+
var DEFAULT_INDEX_PATH = "collection/index.json";
|
|
36
|
+
var DEFAULT_SOURCE_PATH = "collection/source.json";
|
|
37
|
+
var INDEX_MAX_BYTES = 524288;
|
|
38
|
+
var COLLECTION_SOURCE_MAX_BYTES = 2097152;
|
|
39
|
+
var STATEMENT_MAX_BYTES = 2097152;
|
|
40
|
+
var PUBLIC_BUNDLE_MAX_BYTES = 8388608;
|
|
41
|
+
var AUTHORING_BUNDLE_MAX_BYTES = 33554432;
|
|
42
|
+
var MANAGED_MAX_BYTES = 2097152;
|
|
43
|
+
var JUDGE_PACKAGE_MAX_BYTES = 33554432;
|
|
44
|
+
function fail(message) {
|
|
45
|
+
throw new Error(message);
|
|
46
|
+
}
|
|
47
|
+
function normalizedRelativePath(value, label) {
|
|
48
|
+
if (typeof value !== "string" || !value || value.startsWith("/") || value.includes("\\") || value.includes("\0") || value.endsWith("/")) return fail(`${label} must be a normalized relative POSIX path.`);
|
|
49
|
+
if (value.split("/").some((segment) => !segment || segment === "." || segment === "..")) return fail(`${label} must be a normalized relative POSIX path.`);
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function portableOutputPath(value, label) {
|
|
53
|
+
const relative = normalizedRelativePath(value, label);
|
|
54
|
+
if (relative.split("/").some((segment) => !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(segment))) return fail(`${label} must use only portable ASCII path segments.`);
|
|
55
|
+
return relative;
|
|
56
|
+
}
|
|
57
|
+
function resolveInside(root, relativeValue, label) {
|
|
58
|
+
const relative = normalizedRelativePath(relativeValue, label);
|
|
59
|
+
return path.join(root, ...relative.split("/"));
|
|
60
|
+
}
|
|
61
|
+
function collisionKey(value) {
|
|
62
|
+
return value.normalize("NFC").toLocaleLowerCase("en-US");
|
|
63
|
+
}
|
|
64
|
+
async function assertRepositoryRoot(root) {
|
|
65
|
+
const anchoredRoot = await canonicalizeSystemTemporaryPrefix(path.resolve(root));
|
|
66
|
+
if (!await anchoredPathHasNoSymlink(anchoredRoot)) fail("repository root must not traverse a symbolic link.");
|
|
67
|
+
const metadata = await lstat(anchoredRoot);
|
|
68
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory()) fail("repository root must be a real directory.");
|
|
69
|
+
}
|
|
70
|
+
async function readBoundedRepositoryFile(options, relativeValue, label, maximumBytes, expectedBytes) {
|
|
71
|
+
const file = resolveInside(options.root, relativeValue, label);
|
|
72
|
+
if (!await anchoredPathHasNoSymlink(file)) fail(`${label} must not traverse a symbolic link.`);
|
|
73
|
+
let handle;
|
|
74
|
+
try {
|
|
75
|
+
handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error(`${label} must be a readable regular file.`, { cause: error });
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const metadata = await handle.stat();
|
|
81
|
+
if (!metadata.isFile() || metadata.size > maximumBytes || expectedBytes !== void 0 && metadata.size !== expectedBytes) fail(`${label} is outside its allowed byte limit.`);
|
|
82
|
+
return new Uint8Array(await handle.readFile());
|
|
83
|
+
} finally {
|
|
84
|
+
await handle.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function assertOutputPathSafe(options, relativeValue, label) {
|
|
88
|
+
const output = resolveInside(options.root, relativeValue, label);
|
|
89
|
+
if (!await anchoredPathHasNoSymlink(output)) fail(`${label} must not traverse a symbolic link.`);
|
|
90
|
+
let metadata;
|
|
91
|
+
try {
|
|
92
|
+
metadata = await lstat(output);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error.code === "ENOENT") return;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
if (metadata.isSymbolicLink() || !metadata.isFile()) fail(`${label} must replace only a regular file.`);
|
|
98
|
+
}
|
|
99
|
+
async function existingPathIdentity(options, relativeValue, label) {
|
|
100
|
+
const file = resolveInside(options.root, relativeValue, label);
|
|
101
|
+
if (!await anchoredPathHasNoSymlink(file)) fail(`${label} must not traverse a symbolic link.`);
|
|
102
|
+
try {
|
|
103
|
+
const metadata = await lstat(file, { bigint: true });
|
|
104
|
+
if (metadata.isSymbolicLink()) fail(`${label} must not be a symbolic link.`);
|
|
105
|
+
return `${metadata.dev}:${metadata.ino}`;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error.code === "ENOENT") return void 0;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function assertNoFilesystemAliases(options, inputs, outputs) {
|
|
112
|
+
const inputIdentities = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const input of inputs) {
|
|
114
|
+
const identity = await existingPathIdentity(options, input, `declared input '${input}'`);
|
|
115
|
+
if (identity !== void 0 && !inputIdentities.has(identity)) inputIdentities.set(identity, input);
|
|
116
|
+
}
|
|
117
|
+
const outputIdentities = /* @__PURE__ */ new Map();
|
|
118
|
+
for (const output of outputs) {
|
|
119
|
+
const identity = await existingPathIdentity(options, output, `generated output '${output}'`);
|
|
120
|
+
if (identity === void 0) continue;
|
|
121
|
+
const input = inputIdentities.get(identity);
|
|
122
|
+
if (input !== void 0) fail(`generated output '${output}' aliases declared input '${input}' on this filesystem.`);
|
|
123
|
+
const priorOutput = outputIdentities.get(identity);
|
|
124
|
+
if (priorOutput !== void 0) fail(`generated outputs '${priorOutput}' and '${output}' alias on this filesystem.`);
|
|
125
|
+
outputIdentities.set(identity, output);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function ensureOutputParent(options, relativeValue) {
|
|
129
|
+
const relativeParent = path.posix.dirname(normalizedRelativePath(relativeValue, "output path"));
|
|
130
|
+
if (relativeParent === ".") return;
|
|
131
|
+
let current = options.root;
|
|
132
|
+
for (const segment of relativeParent.split("/")) {
|
|
133
|
+
current = path.join(current, segment);
|
|
134
|
+
try {
|
|
135
|
+
const metadata = await lstat(current);
|
|
136
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory()) fail(`output directory '${relativeParent}' must contain only real directories.`);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error.code !== "ENOENT") throw error;
|
|
139
|
+
try {
|
|
140
|
+
await mkdir(current);
|
|
141
|
+
} catch (mkdirError) {
|
|
142
|
+
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
143
|
+
const metadata = await lstat(current);
|
|
144
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory()) fail(`output directory '${relativeParent}' must contain only real directories.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function atomicRepositoryWrite(options, relativeValue, bytes) {
|
|
150
|
+
const output = resolveInside(options.root, relativeValue, "output path");
|
|
151
|
+
await ensureOutputParent(options, relativeValue);
|
|
152
|
+
const temporary = `${output}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
153
|
+
try {
|
|
154
|
+
await writeFile(temporary, bytes, {
|
|
155
|
+
flag: "wx",
|
|
156
|
+
mode: 420
|
|
157
|
+
});
|
|
158
|
+
await rename(temporary, output);
|
|
159
|
+
} finally {
|
|
160
|
+
await rm(temporary, { force: true });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function parseOptions(arguments_) {
|
|
164
|
+
const [commandValue, ...rest] = arguments_;
|
|
165
|
+
if (commandValue !== "build" && commandValue !== "verify") return fail("Usage: woj organizer collection <build|verify> [repository-root] [--index path] [--source path] [--managed path] [--managed-source path]");
|
|
166
|
+
let root = ".";
|
|
167
|
+
let indexPath = DEFAULT_INDEX_PATH;
|
|
168
|
+
let sourcePath = DEFAULT_SOURCE_PATH;
|
|
169
|
+
let managedPath;
|
|
170
|
+
let managedSourcePath;
|
|
171
|
+
let sawRoot = false;
|
|
172
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
173
|
+
const argument = rest[index];
|
|
174
|
+
if (argument === "--index" || argument === "--source" || argument === "--managed" || argument === "--managed-source") {
|
|
175
|
+
const value = rest[index + 1];
|
|
176
|
+
if (!value) return fail(`${argument} requires a path.`);
|
|
177
|
+
if (argument === "--index") indexPath = portableOutputPath(value, "index path");
|
|
178
|
+
else if (argument === "--source") sourcePath = normalizedRelativePath(value, "source path");
|
|
179
|
+
else if (argument === "--managed") managedPath = portableOutputPath(value, "managed contract path");
|
|
180
|
+
else managedSourcePath = normalizedRelativePath(value, "managed source path");
|
|
181
|
+
index += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (argument?.startsWith("-")) return fail(`Unknown option '${argument}'.`);
|
|
185
|
+
if (sawRoot || !argument) return fail("Only one repository root may be provided.");
|
|
186
|
+
root = argument;
|
|
187
|
+
sawRoot = true;
|
|
188
|
+
}
|
|
189
|
+
if (commandValue !== "build" && managedSourcePath) return fail("--managed-source is only valid for build.");
|
|
190
|
+
if (commandValue === "build" && managedPath && !managedSourcePath) return fail("--managed requires --managed-source when building.");
|
|
191
|
+
if (commandValue === "build" && collisionKey(indexPath) === collisionKey(sourcePath)) return fail("--index and --source must not be the same input and output path.");
|
|
192
|
+
if (commandValue === "build" && managedPath !== void 0 && managedSourcePath !== void 0 && collisionKey(managedPath) === collisionKey(managedSourcePath)) return fail("--managed and --managed-source must not be the same input and output path.");
|
|
193
|
+
return {
|
|
194
|
+
command: commandValue,
|
|
195
|
+
root: path.resolve(root),
|
|
196
|
+
indexPath,
|
|
197
|
+
sourcePath,
|
|
198
|
+
...managedPath ? { managedPath } : {},
|
|
199
|
+
...managedSourcePath ? { managedSourcePath } : {}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function isRecord(value) {
|
|
203
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
204
|
+
}
|
|
205
|
+
function exactKeys(value, expected, label) {
|
|
206
|
+
const actual = Object.keys(value).sort();
|
|
207
|
+
const sortedExpected = [...expected].sort();
|
|
208
|
+
if (JSON.stringify(actual) !== JSON.stringify(sortedExpected)) fail(`${label} must contain exactly: ${sortedExpected.join(", ")}.`);
|
|
209
|
+
}
|
|
210
|
+
function parseAuthoredCollection(value) {
|
|
211
|
+
if (!isRecord(value)) return fail("collection/source.json must be an object.");
|
|
212
|
+
exactKeys(value, [
|
|
213
|
+
"schema",
|
|
214
|
+
"localization",
|
|
215
|
+
"problems"
|
|
216
|
+
], "collection source");
|
|
217
|
+
if (value.schema !== SOURCE_SCHEMA) return fail(`collection source schema must be '${SOURCE_SCHEMA}'.`);
|
|
218
|
+
if (!isRecord(value.localization)) return fail("collection localization must be an object.");
|
|
219
|
+
exactKeys(value.localization, ["defaultLocale", "supportedLocales"], "collection localization");
|
|
220
|
+
if (value.localization.defaultLocale !== "zh-TW" || JSON.stringify(value.localization.supportedLocales) !== JSON.stringify(["zh-TW", "en"])) return fail("collection localization must declare zh-TW followed by en.");
|
|
221
|
+
if (!Array.isArray(value.problems) || value.problems.length < 1 || value.problems.length > 1e3) return fail("collection source must contain between 1 and 1000 problems.");
|
|
222
|
+
return {
|
|
223
|
+
schema: SOURCE_SCHEMA,
|
|
224
|
+
localization: {
|
|
225
|
+
defaultLocale: "zh-TW",
|
|
226
|
+
supportedLocales: ["zh-TW", "en"]
|
|
227
|
+
},
|
|
228
|
+
problems: value.problems.map((problemValue, index) => {
|
|
229
|
+
if (!isRecord(problemValue)) return fail(`collection source problem ${index + 1} must be an object.`);
|
|
230
|
+
exactKeys(problemValue, ["statementPaths", "bundlePath"], `collection source problem ${index + 1}`);
|
|
231
|
+
if (!isRecord(problemValue.statementPaths)) return fail(`problem ${index + 1} statementPaths must be an object.`);
|
|
232
|
+
exactKeys(problemValue.statementPaths, ["zh-TW", "en"], `problem ${index + 1} statementPaths`);
|
|
233
|
+
const statementPaths = {
|
|
234
|
+
"zh-TW": normalizedRelativePath(problemValue.statementPaths["zh-TW"], `problem ${index + 1} zh-TW statement`),
|
|
235
|
+
en: normalizedRelativePath(problemValue.statementPaths.en, `problem ${index + 1} English statement`)
|
|
236
|
+
};
|
|
237
|
+
if (!statementPaths["zh-TW"].endsWith(".md") || !statementPaths.en.endsWith(".md")) return fail(`problem ${index + 1} statements must be Markdown files.`);
|
|
238
|
+
return {
|
|
239
|
+
statementPaths,
|
|
240
|
+
bundlePath: normalizedRelativePath(problemValue.bundlePath, `problem ${index + 1} bundle`)
|
|
241
|
+
};
|
|
242
|
+
})
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function parseJson(bytes, label) {
|
|
246
|
+
let text;
|
|
247
|
+
try {
|
|
248
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw new Error(`${label} is not valid UTF-8.`, { cause: error });
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
return JSON.parse(text);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
throw new Error(`${label} is not valid JSON.`, { cause: error });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function sha256Hex(bytes) {
|
|
259
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes.slice().buffer);
|
|
260
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
261
|
+
}
|
|
262
|
+
async function validatePublishedCollection(options, strict) {
|
|
263
|
+
const indexBytes = await readBoundedRepositoryFile(options, options.indexPath, "index path", INDEX_MAX_BYTES);
|
|
264
|
+
const index = parseProblemCollectionIndex$1(parseJson(indexBytes, options.indexPath));
|
|
265
|
+
await verifyProblemCollectionRevision$1(index);
|
|
266
|
+
if (strict && !Buffer.from(indexBytes).equals(Buffer.from(canonicalJsonBytes(index)))) fail(`${options.indexPath} is not canonical; run woj organizer collection build.`);
|
|
267
|
+
const indexDirectory = path.posix.dirname(options.indexPath);
|
|
268
|
+
for (const entry of index.problems) {
|
|
269
|
+
for (const [locale, statementPath] of Object.entries(entry.statementPaths)) if ((await readBoundedRepositoryFile(options, statementPath, `problem '${entry.id}' ${locale} statement`, STATEMENT_MAX_BYTES)).byteLength < 1) fail(`problem '${entry.id}' ${locale} statement must contain between 1 byte and 2 MiB.`);
|
|
270
|
+
const repositoryPath = path.posix.join(indexDirectory, entry.bundle.path);
|
|
271
|
+
const bytes = await readBoundedRepositoryFile(options, repositoryPath, `problem '${entry.id}' bundle`, PUBLIC_BUNDLE_MAX_BYTES, entry.bundle.bytes);
|
|
272
|
+
const problem = await verifyProblemBundleBytes$1(bytes, entry);
|
|
273
|
+
if (strict && !Buffer.from(bytes).equals(Buffer.from(canonicalJsonBytes({
|
|
274
|
+
schema: BROWSER_PROBLEM_SCHEMA$1,
|
|
275
|
+
problem
|
|
276
|
+
})))) fail(`${repositoryPath} is not canonical; run woj organizer collection build.`);
|
|
277
|
+
}
|
|
278
|
+
if (strict) await rejectUndeclaredContentAddressedBundles(options, index);
|
|
279
|
+
if (options.managedPath) await validateManagedContract(options, index);
|
|
280
|
+
return index;
|
|
281
|
+
}
|
|
282
|
+
async function validateManagedContract(options, index) {
|
|
283
|
+
const managedPath = options.managedPath;
|
|
284
|
+
if (!managedPath) fail("managed contract path is required.");
|
|
285
|
+
const contractBytes = await readBoundedRepositoryFile(options, managedPath, "managed contract path", MANAGED_MAX_BYTES);
|
|
286
|
+
const contract = parseManagedCollectionV2$1(contractBytes);
|
|
287
|
+
if (contract.collectionRevision !== index.revision) fail("managed collection revision does not match collection/index.json.");
|
|
288
|
+
if (JSON.stringify(contract.problems.map((problem) => problem.slug)) !== JSON.stringify(index.problems.map((problem) => problem.id))) fail("managed collection problems must exactly match index order.");
|
|
289
|
+
const indexDirectory = path.posix.dirname(options.indexPath);
|
|
290
|
+
for (const [position, publication] of contract.problems.entries()) {
|
|
291
|
+
const entry = index.problems[position];
|
|
292
|
+
const practiceBytes = await readBoundedRepositoryFile(options, path.posix.join(indexDirectory, entry.bundle.path), `problem '${entry.id}' bundle`, PUBLIC_BUNDLE_MAX_BYTES, entry.bundle.bytes);
|
|
293
|
+
const practice = await verifyProblemBundleBytes$1(practiceBytes, entry);
|
|
294
|
+
const contestBytes = await readPublishedObject(options, indexDirectory, publication.contestPublic, `contest-public '${publication.slug}'`, PUBLIC_BUNDLE_MAX_BYTES);
|
|
295
|
+
const expectedContestBytes = contestPublicProjectionBytes(practice, entry.bundle.sha256);
|
|
296
|
+
if (!Buffer.from(contestBytes).equals(Buffer.from(expectedContestBytes))) fail(`contest-public '${publication.slug}' is not the deterministic projection of its practice bundle.`);
|
|
297
|
+
const packageBytes = await readPublishedObject(options, indexDirectory, publication.judgePackage, `judge package '${publication.slug}'`, JUDGE_PACKAGE_MAX_BYTES);
|
|
298
|
+
const validatedPackage = await validateJudgePackage(packageBytes, {
|
|
299
|
+
expectedBytes: publication.judgePackage.bytes,
|
|
300
|
+
expectedSha256: publication.judgePackage.sha256,
|
|
301
|
+
memoryLimitBytes: Math.max(...practice.scoring.policies.map((policy) => policy.limits.memoryLimitBytes))
|
|
302
|
+
});
|
|
303
|
+
if (JSON.stringify(validatedPackage.manifest.allowedProfiles) !== JSON.stringify(publication.allowedProfiles)) fail(`judge package '${publication.slug}' allowedProfiles disagree with collection/managed.json.`);
|
|
304
|
+
assertJudgeDataMatchesPracticePublic(validatedPackage.judgeData, practice, Object.keys(publication.allowedProfiles));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function readPublishedObject(options, indexDirectory, object, label, maximumBytes) {
|
|
308
|
+
const bytes = await readBoundedRepositoryFile(options, path.posix.join(indexDirectory, object.repositoryPath), label, maximumBytes, object.bytes);
|
|
309
|
+
if (await sha256Hex(bytes) !== object.sha256) fail(`${label} failed integrity verification.`);
|
|
310
|
+
return bytes;
|
|
311
|
+
}
|
|
312
|
+
async function rejectUndeclaredContentAddressedBundles(options, index) {
|
|
313
|
+
const indexDirectory = path.posix.dirname(options.indexPath);
|
|
314
|
+
const relativeDirectory = path.posix.join(indexDirectory, "problems");
|
|
315
|
+
const directory = resolveInside(options.root, relativeDirectory, "bundle directory");
|
|
316
|
+
if (!await anchoredPathHasNoSymlink(directory)) fail("bundle directory must not traverse a symbolic link.");
|
|
317
|
+
const metadata = await lstat(directory);
|
|
318
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory()) fail("bundle directory must be a real directory.");
|
|
319
|
+
const declared = new Set(index.problems.map((entry) => path.posix.basename(entry.bundle.path)));
|
|
320
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
321
|
+
const digestPattern = /\.[0-9a-f]{64}\.json$/;
|
|
322
|
+
const undeclared = entries.filter((entry) => entry.isFile() && digestPattern.test(entry.name) && !declared.has(entry.name)).map((entry) => entry.name).sort();
|
|
323
|
+
const unsafe = entries.filter((entry) => entry.isSymbolicLink() || !entry.isFile() && !entry.isDirectory()).map((entry) => entry.name).sort();
|
|
324
|
+
if (unsafe.length > 0) fail(`bundle directory contains unsafe entries: ${unsafe.join(", ")}.`);
|
|
325
|
+
if (undeclared.length > 0) fail(`undeclared content-addressed bundles: ${undeclared.join(", ")}.`);
|
|
326
|
+
}
|
|
327
|
+
function assertGeneratedSize(bytes, maximumBytes, label) {
|
|
328
|
+
if (bytes.byteLength > maximumBytes) fail(`${label} exceeds its allowed byte limit.`);
|
|
329
|
+
}
|
|
330
|
+
async function prepareCollection(options) {
|
|
331
|
+
const source = parseAuthoredCollection(parseJson(await readBoundedRepositoryFile(options, options.sourcePath, "source path", COLLECTION_SOURCE_MAX_BYTES), options.sourcePath));
|
|
332
|
+
const indexDirectory = path.posix.dirname(options.indexPath);
|
|
333
|
+
const entries = [];
|
|
334
|
+
const authoredProblems = [];
|
|
335
|
+
const practiceProblems = [];
|
|
336
|
+
const inputs = /* @__PURE__ */ new Set([options.sourcePath]);
|
|
337
|
+
const outputs = [];
|
|
338
|
+
for (const [position, authored] of source.problems.entries()) {
|
|
339
|
+
inputs.add(authored.bundlePath);
|
|
340
|
+
const authoredBytes = await readBoundedRepositoryFile(options, authored.bundlePath, `problem ${position + 1} source bundle`, AUTHORING_BUNDLE_MAX_BYTES);
|
|
341
|
+
const problem = parseStandaloneProblemBundle$1(parseJson(authoredBytes, authored.bundlePath));
|
|
342
|
+
if (problem.number !== position + 1) fail(`problem '${problem.id}' must have number ${position + 1}.`);
|
|
343
|
+
for (const [locale, statementPath] of Object.entries(authored.statementPaths)) {
|
|
344
|
+
inputs.add(statementPath);
|
|
345
|
+
if ((await readBoundedRepositoryFile(options, statementPath, `problem '${problem.id}' ${locale} statement`, STATEMENT_MAX_BYTES)).byteLength < 1) fail(`problem '${problem.id}' ${locale} statement must contain between 1 byte and 2 MiB.`);
|
|
346
|
+
}
|
|
347
|
+
authoredProblems.push(problem);
|
|
348
|
+
const practice = derivePracticePublic(problem);
|
|
349
|
+
practiceProblems.push(practice);
|
|
350
|
+
const bundleBytes = canonicalJsonBytes({
|
|
351
|
+
schema: BROWSER_PROBLEM_SCHEMA$1,
|
|
352
|
+
problem: practice
|
|
353
|
+
});
|
|
354
|
+
assertGeneratedSize(bundleBytes, PUBLIC_BUNDLE_MAX_BYTES, `problem '${problem.id}' public bundle`);
|
|
355
|
+
const digest = await sha256Hex(bundleBytes);
|
|
356
|
+
const bundleName = `${String(problem.number).padStart(3, "0")}-${problem.id}.${digest}.json`;
|
|
357
|
+
outputs.push({
|
|
358
|
+
path: path.posix.join(indexDirectory, "problems", bundleName),
|
|
359
|
+
label: `problem '${problem.id}' public bundle output`,
|
|
360
|
+
bytes: bundleBytes
|
|
361
|
+
});
|
|
362
|
+
entries.push({
|
|
363
|
+
id: problem.id,
|
|
364
|
+
number: problem.number,
|
|
365
|
+
title: problem.title,
|
|
366
|
+
trackId: problem.trackId,
|
|
367
|
+
track: problem.track,
|
|
368
|
+
statementPaths: authored.statementPaths,
|
|
369
|
+
difficulty: problem.difficulty,
|
|
370
|
+
tags: problem.tags,
|
|
371
|
+
caseCount: practice.judgeCases.length,
|
|
372
|
+
bundle: {
|
|
373
|
+
path: `problems/${bundleName}`,
|
|
374
|
+
sha256: digest,
|
|
375
|
+
bytes: bundleBytes.byteLength
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const withoutRevision = {
|
|
380
|
+
schema: BROWSER_COLLECTION_SCHEMA$1,
|
|
381
|
+
problemSchema: BROWSER_PROBLEM_SCHEMA$1,
|
|
382
|
+
localization: source.localization,
|
|
383
|
+
problems: entries
|
|
384
|
+
};
|
|
385
|
+
const index = parseProblemCollectionIndex$1({
|
|
386
|
+
...withoutRevision,
|
|
387
|
+
revision: await problemCollectionRevision$1(withoutRevision)
|
|
388
|
+
});
|
|
389
|
+
const indexBytes = canonicalJsonBytes(index);
|
|
390
|
+
assertGeneratedSize(indexBytes, INDEX_MAX_BYTES, "collection index output");
|
|
391
|
+
outputs.push({
|
|
392
|
+
path: options.indexPath,
|
|
393
|
+
label: "collection index output",
|
|
394
|
+
bytes: indexBytes
|
|
395
|
+
});
|
|
396
|
+
return {
|
|
397
|
+
index,
|
|
398
|
+
authoredProblems,
|
|
399
|
+
practiceProblems,
|
|
400
|
+
inputs,
|
|
401
|
+
outputs
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
async function readDeclaredManagedSourceObject(options, object, label, inputs) {
|
|
405
|
+
inputs.add(object.path);
|
|
406
|
+
const bytes = await readBoundedRepositoryFile(options, object.path, label, JUDGE_PACKAGE_MAX_BYTES, object.bytes);
|
|
407
|
+
if (await sha256Hex(bytes) !== object.sha256) fail(`${label} failed declared size or digest verification.`);
|
|
408
|
+
return bytes;
|
|
409
|
+
}
|
|
410
|
+
async function managedJudgeInput(options, problem, inputs) {
|
|
411
|
+
if (problem.judge.kind === "text") return { kind: "text" };
|
|
412
|
+
const artifact = await readDeclaredManagedSourceObject(options, problem.judge.artifact, `${problem.judge.kind} artifact '${problem.slug}'`, inputs);
|
|
413
|
+
const assets = [];
|
|
414
|
+
for (const asset of problem.judge.assets) assets.push({
|
|
415
|
+
guestPath: asset.guestPath,
|
|
416
|
+
contents: await readDeclaredManagedSourceObject(options, asset, `${problem.judge.kind} asset '${problem.slug}/${asset.path}'`, inputs)
|
|
417
|
+
});
|
|
418
|
+
return problem.judge.kind === "checker" ? {
|
|
419
|
+
kind: "checker",
|
|
420
|
+
runtimeProfile: problem.judge.artifact.runtimeProfile,
|
|
421
|
+
artifact,
|
|
422
|
+
assets,
|
|
423
|
+
args: problem.judge.args
|
|
424
|
+
} : {
|
|
425
|
+
kind: "interactive",
|
|
426
|
+
runtimeProfile: problem.judge.artifact.runtimeProfile,
|
|
427
|
+
artifact,
|
|
428
|
+
assets,
|
|
429
|
+
args: problem.judge.args,
|
|
430
|
+
inputPath: problem.judge.inputPath
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
async function prepareManagedCollection(options, built) {
|
|
434
|
+
const managedSourcePath = options.managedSourcePath;
|
|
435
|
+
if (!managedSourcePath) fail("managed source path is required.");
|
|
436
|
+
const inputs = /* @__PURE__ */ new Set([managedSourcePath]);
|
|
437
|
+
const outputs = [];
|
|
438
|
+
const sourceBytes = await readBoundedRepositoryFile(options, managedSourcePath, "managed source path", MANAGED_MAX_BYTES);
|
|
439
|
+
const source = parseManagedCollectionSource$1(parseJson(sourceBytes, managedSourcePath));
|
|
440
|
+
if (JSON.stringify(source.problems.map((problem) => problem.slug)) !== JSON.stringify(built.index.problems.map((problem) => problem.id))) fail("managed source problems must exactly match collection/index.json order.");
|
|
441
|
+
const indexDirectory = path.posix.dirname(options.indexPath);
|
|
442
|
+
const publicationDirectory = path.posix.join(indexDirectory, "managed");
|
|
443
|
+
const publications = [];
|
|
444
|
+
for (const [position, sourceProblem] of source.problems.entries()) {
|
|
445
|
+
const entry = built.index.problems[position];
|
|
446
|
+
const practice = built.practiceProblems[position];
|
|
447
|
+
const authored = built.authoredProblems[position];
|
|
448
|
+
if (!authored || authored.id !== entry.id) fail(`authoring source for '${entry.id}' is unavailable.`);
|
|
449
|
+
const contestBytes = contestPublicProjectionBytes(practice, entry.bundle.sha256);
|
|
450
|
+
assertGeneratedSize(contestBytes, PUBLIC_BUNDLE_MAX_BYTES, `contest-public '${entry.id}' output`);
|
|
451
|
+
const contestSha256 = await sha256Hex(contestBytes);
|
|
452
|
+
const contestName = `${String(entry.number).padStart(3, "0")}-${entry.id}.${contestSha256}.contest.json`;
|
|
453
|
+
outputs.push({
|
|
454
|
+
path: path.posix.join(publicationDirectory, contestName),
|
|
455
|
+
label: `contest-public '${entry.id}' output`,
|
|
456
|
+
bytes: contestBytes
|
|
457
|
+
});
|
|
458
|
+
const encoded = await encodeJudgePackage({
|
|
459
|
+
judgeData: deriveJudgeData(authored, Object.keys(sourceProblem.allowedProfiles)),
|
|
460
|
+
allowedProfiles: sourceProblem.allowedProfiles,
|
|
461
|
+
judge: await managedJudgeInput(options, sourceProblem, inputs)
|
|
462
|
+
});
|
|
463
|
+
assertGeneratedSize(encoded.bytes, JUDGE_PACKAGE_MAX_BYTES, `judge package '${entry.id}' output`);
|
|
464
|
+
const validated = await validateJudgePackage(encoded.bytes, {
|
|
465
|
+
expectedBytes: encoded.bytes.byteLength,
|
|
466
|
+
expectedSha256: encoded.executionSemanticSha256,
|
|
467
|
+
memoryLimitBytes: Math.max(...practice.scoring.policies.map((policy) => policy.limits.memoryLimitBytes))
|
|
468
|
+
});
|
|
469
|
+
assertJudgeDataMatchesPracticePublic(validated.judgeData, practice, Object.keys(sourceProblem.allowedProfiles));
|
|
470
|
+
const packageName = `${String(entry.number).padStart(3, "0")}-${entry.id}.${encoded.executionSemanticSha256}.wasmojjudge`;
|
|
471
|
+
outputs.push({
|
|
472
|
+
path: path.posix.join(publicationDirectory, packageName),
|
|
473
|
+
label: `judge package '${entry.id}' output`,
|
|
474
|
+
bytes: encoded.bytes
|
|
475
|
+
});
|
|
476
|
+
publications.push({
|
|
477
|
+
slug: entry.id,
|
|
478
|
+
allowedProfiles: sourceProblem.allowedProfiles,
|
|
479
|
+
contestPublic: {
|
|
480
|
+
repositoryPath: `managed/${contestName}`,
|
|
481
|
+
bytes: contestBytes.byteLength,
|
|
482
|
+
sha256: contestSha256
|
|
483
|
+
},
|
|
484
|
+
judgePackage: {
|
|
485
|
+
repositoryPath: `managed/${packageName}`,
|
|
486
|
+
bytes: encoded.bytes.byteLength,
|
|
487
|
+
sha256: encoded.executionSemanticSha256
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
const contract = parseManagedCollectionV2$1(canonicalJsonBytes({
|
|
492
|
+
schema: MANAGED_COLLECTION_SCHEMA$1,
|
|
493
|
+
collectionRevision: built.index.revision,
|
|
494
|
+
problems: publications
|
|
495
|
+
}));
|
|
496
|
+
const managedPath = options.managedPath ?? path.posix.join(indexDirectory, "managed.json");
|
|
497
|
+
const contractBytes = canonicalJsonBytes(contract);
|
|
498
|
+
assertGeneratedSize(contractBytes, MANAGED_MAX_BYTES, "managed contract output");
|
|
499
|
+
outputs.push({
|
|
500
|
+
path: managedPath,
|
|
501
|
+
label: "managed contract output",
|
|
502
|
+
bytes: contractBytes
|
|
503
|
+
});
|
|
504
|
+
return {
|
|
505
|
+
inputs,
|
|
506
|
+
outputs
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function pathsOverlap(left, right) {
|
|
510
|
+
const leftKey = collisionKey(left);
|
|
511
|
+
const rightKey = collisionKey(right);
|
|
512
|
+
return leftKey === rightKey || leftKey.startsWith(`${rightKey}/`) || rightKey.startsWith(`${leftKey}/`);
|
|
513
|
+
}
|
|
514
|
+
async function writePreparedCollection(options, inputs, outputs) {
|
|
515
|
+
const outputPaths = /* @__PURE__ */ new Set();
|
|
516
|
+
for (const output of outputs) {
|
|
517
|
+
portableOutputPath(output.path, output.label);
|
|
518
|
+
const key = collisionKey(output.path);
|
|
519
|
+
if (outputPaths.has(key)) fail(`generated output '${output.path}' has a case-folded or normalized collision.`);
|
|
520
|
+
outputPaths.add(key);
|
|
521
|
+
for (const input of inputs) if (pathsOverlap(input, output.path)) fail(`generated output '${output.path}' overlaps declared input '${input}'.`);
|
|
522
|
+
}
|
|
523
|
+
await assertNoFilesystemAliases(options, inputs, outputs.map((output) => output.path));
|
|
524
|
+
await Promise.all(outputs.map((output) => assertOutputPathSafe(options, output.path, output.label)));
|
|
525
|
+
for (const output of outputs) await atomicRepositoryWrite(options, output.path, output.bytes);
|
|
526
|
+
}
|
|
527
|
+
async function runCollectionCli(arguments_) {
|
|
528
|
+
const options = parseOptions(arguments_);
|
|
529
|
+
await assertRepositoryRoot(options.root);
|
|
530
|
+
let index;
|
|
531
|
+
if (options.command === "build") {
|
|
532
|
+
const initialManagedOutput = options.managedSourcePath ? options.managedPath ?? path.posix.join(path.posix.dirname(options.indexPath), "managed.json") : void 0;
|
|
533
|
+
await assertNoFilesystemAliases(options, /* @__PURE__ */ new Set([options.sourcePath, ...options.managedSourcePath ? [options.managedSourcePath] : []]), [options.indexPath, ...initialManagedOutput ? [initialManagedOutput] : []]);
|
|
534
|
+
const built = await prepareCollection(options);
|
|
535
|
+
index = built.index;
|
|
536
|
+
const managed = options.managedSourcePath ? await prepareManagedCollection(options, built) : void 0;
|
|
537
|
+
await writePreparedCollection(options, /* @__PURE__ */ new Set([...built.inputs, ...managed?.inputs ?? []]), [...built.outputs, ...managed?.outputs ?? []]);
|
|
538
|
+
} else index = await validatePublishedCollection(options, true);
|
|
539
|
+
process.stdout.write(`${options.command} ok: ${index.problems.length} problems, revision ${index.revision}\n`);
|
|
540
|
+
}
|
|
541
|
+
if (process.argv[1] ? import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href : false) runCollectionCli(process.argv.slice(2)).catch((error) => {
|
|
542
|
+
process.stderr.write(`woj organizer collection: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
543
|
+
process.exitCode = 1;
|
|
544
|
+
});
|
|
545
|
+
//#endregion
|
|
546
|
+
export { BROWSER_COLLECTION_SCHEMA, BROWSER_PROBLEM_SCHEMA, MANAGED_COLLECTION_SCHEMA, parseManagedCollectionContract, parseManagedCollectionSource, parseManagedCollectionV2, parseManagedCollectionValueV2, parseProblemCollectionIndex, parseStandaloneProblemBundle, problemCollectionRevision, runCollectionCli, verifyProblemBundleBytes, verifyProblemCollectionRevision };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arjun Barrett
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wasm-oj/organizer",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Problem collection and immutable judge-data publication tools for WASM-OJ.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "JacobLinCool",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/wasm-oj/forge.git",
|
|
10
|
+
"directory": "packages/organizer"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/wasm-oj/forge#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/wasm-oj/forge/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"online-judge",
|
|
18
|
+
"problems",
|
|
19
|
+
"contest"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org/"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE",
|
|
39
|
+
"THIRD_PARTY_NOTICES.md",
|
|
40
|
+
"licenses"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=24.18.0 <25"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@wasm-oj/core": "0.2.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "node ../../scripts/build-library.mjs --package @wasm-oj/organizer"
|
|
50
|
+
}
|
|
51
|
+
}
|