A browser app that turns a local folder of resources into an RO-Crate — reading the folder and writing three outputs back into it:
ro-crate-metadata.json— the crate as JSON-LDro-crate-metadata.xlsx— the crate as a spreadsheet (viaro-crate-excel)ro-crate-preview.html— a self-contained HTML preview (viaro-crate-static-site)
It reads and writes local files through the File System Access API (Chrome / Edge), so
the user's files never leave their machine. Unlike the earlier single-file version, this
one uses the ro-crate library to assemble the crate — the same approach as
crate-o and
corpus-tools-dyirbal —
rather than hand-building JSON-LD. Because those libraries are npm packages, the app is now
a small Vite project that bundles them for the browser.
Requires Node and npm (for the build). The end result runs in Chrome/Edge.
Build plugins live in a sibling repo, c2c-plugins — clone it next to this one (../c2c-plugins) before npm install (see package.json's "c2c-plugins": "file:../c2c-plugins" dependency, and SPEC.md §4.7a).
cd chaos2crate
npm install # pulls ro-crate, ro-crate-excel, ro-crate-static-site, exceljs, vite, c2c-plugins
npm run dev # dev server at http://localhost:5173 → open in Chrome/Edge
# or
npm run build # produces dist/ (a static site) — every plugin, by default
# or, to bundle only some plugins (smaller dist/):
PLUGINS=merge,validate-crate,ro-crate-json-output npm run build
npm run preview # serve the built dist/ at http://localhost:5000PLUGINS entries can also pull a plugin from somewhere other than c2c-plugins — another repo (built the same way, added to package.json as its own dependency) or a one-off local file:
# a plugin from another repo built like c2c-plugins (its own src/<name>/index.js)
PLUGINS=merge,special=other-plugins npm run build
# a plugin file you're testing locally, not wired into package.json at all
PLUGINS=merge,scratch=../scratch-plugin/index.js npm run buildSee scripts/select-plugins.mjs's header comment and SPEC.md §4.7a for the full syntax.
The File System Access API needs a secure context (http://localhost or https://), so a
file:// open won't work. To deploy, npm run build and host the dist/ folder on any
static HTTPS host — the end-user experience is then zero-install.
Re-verify the crate pipeline any time with
node test-crate.mjs(afternpm install): it builds a crate from a synthetic file list and confirms the JSON, xlsx, and html all generate. This is exactly how the pipeline was validated against the real libraries.
The Build card walks through four steps:
- Choose folder — pick a local corpus folder (read + write).
- Select profile — choose a MASP profile (fetched from
Language-Research-Technology/c2c-masp-profiles) that matches this data; it determines which fields Describe asks for and which Build options are shown. - Describe — fill in the root-dataset fields the selected profile's schema asks for (name, description, license, etc.).
- Build — scan the folder and generate the three RO-Crate outputs.
Separately, for a folder that already has a crate: the Show card displays the existing
ro-crate-preview.html/ro-crate-metadata.json (offering to build one if missing), and the
Edit card lets you edit entities/properties directly and save back to
ro-crate-metadata.json. Both have a "Build" shortcut in the context bar to rebuild in place —
it still requires a profile to be selected first (redirecting to Select profile if none is).
src/crate.js is a dependency-light, isomorphic module (runs in the browser and Node)
that assembles the crate using the ROCrate class (generic-folder mode; there's a separate
src/plugins/docx-input/docx_crate.js path for Structured Word Documents mode — see Settings).
Which one runs is an input-mode plugin dispatch (src/plugins/index.js's INPUT_PLUGINS,
keyed by inputMode) — a different registry from the additive hook-tapping plugins below,
since input mode is mutually exclusive rather than something multiple plugins can all tap:
- root dataset, its
@type/conformsToand the metadata descriptor's license all come from the selected profile plus the Describe step (see "Configuration" below) —pcdm:hasMember/hasPartlink it to oneRepositoryObjectper top-level folder (standalone top-level files get a synthetic object), or, in Collections mode, aRepositoryCollectionwith child folder objects and aFilesobject for direct files; - one
Fileentity per file (@id= relative path,isPartOf); which custom fields (e.g.custom:participant,custom:possibleDuplicate) get blank-initialized on every file, and theirrdf:Propertydefinitions, also come from the selected profile (the profile's mode file'sfileProperties) — nothing is added unconditionally; - hash
@ids ofRepositoryObjects rewritten toarcp://…/<name>on export; - optional AUSTLANG subject-language identification (filename-based; see options) — its own
rdf:Propertydefinitions (custom:austlangCodeetc.) are only added when a language was actually identified, not for every build.
The crate object is then serialized with crate.getJson() (JSON), fed to ro-crate-excel's
Workbook (xlsx), and to ro-crate-static-site's renderSinglePage/renderTemplate/
renderMultiPage (html, using the selected profile's own property-group layout).
| option | effect |
|---|---|
| Identify subject languages (AUSTLANG, by filename) | the original's -l; filename-based only; uses the bundled AUSTLANG data pack offline |
| …also match AUSTLANG alternate names | the original's -a |
| Merge metadata from a spreadsheet | upload an .xlsx and merge rows into crate entities by matching @id |
| Spreadsheet (XLSX) | the workbook used for merge; can contain multiple sheets |
| Build mapping from spreadsheet columns… | opens a mapping popup to set source → target property mappings (plus optional entity type) |
| Generate ro-crate-preview.html | write the HTML preview (on by default) |
| Template from rocss-templates | pick a folder from Language-Research-Technology/rocss-templates; downloads and uses that folder's template config |
| Upload template files | upload a single config.json; template and style are resolved from values inside that config |
Which of these are actually shown depends on the selected profile's mode file
(tools.chaos2crate.buildOptions.enabledOptionKeys).
Accessed via the ⚙ button in the Build view; these are app/session preferences rather than per-build options, so they live outside the Build panel:
| setting | effect |
|---|---|
| Input type | Generic folder of files, or Structured Word documents (.docx) — see docs/docx-authoring.md for how to write .docx files this mode parses |
| Theme | Light or dark |
| Top-level folders are: Objects or Collections | Objects = existing behavior (RepositoryObject); Collections = RepositoryCollection with child folder objects and a Files object for direct files |
| Overwrite existing outputs | off = skip files that already exist |
| Enable local template upload | shows/hides the "Upload template files" Build option |
| Generate ro-crate-metadata.xlsx | write the spreadsheet output (on by default) |
Template repo fetches currently use a minimal/public access method: a plain GitHub Contents API request (with an Accept header) plus raw file downloads, with no Authorization token.
Future work: add optional token-based auth support to improve rate-limit headroom and private-repo access.
Future work: surface an explicit note/example in the merge UI for placeLookup.providers so users can pin lookups to a single source such as Geoscience Australia for deterministic results.
- Merge applies before output generation, so JSON/xlsx/html all include merged values.
- Merge mapping config precedence: mapping popup upload →
merge-config.jsonin folder → bundledsrc/merge_config.json. - Mapping popup supports workbook sheet selection; source columns refresh for the selected sheet.
- If a mapping config includes
sheet, that sheet is selected and its headers are used. - When a mapping config is loaded in the popup, rows are restricted to config-defined source fields (badge: “Showing config-defined sources”).
- Prefixed mapping targets (for example
dc:format) trigger workbook context lookup; missing contexts found in workbook are added to the crate context. - Typed
Placemappings now try to add a linkedGeometryentity during merge. Geometry entities are stored with@id,@type,.latitude,.longitude, andasWKT, and linked from the place viageo. - Merge config may include an optional
placeLookupblock withenabled,providers,records,ghap, andgeoscienceAustraliasettings, plusplaceMatchRegion(for exampleQLD) to prefer candidates from a specific state/region when names are ambiguous. Manualrecordsare checked first; by default live lookup now tries Geoscience Australia’s Composite Gazetteer ArcGIS service before GHAP/TLCMap-style endpoints. placeMatchRegioncan also be set at the merge-config root level (outsideplaceLookup) for convenience; it is forwarded into place lookup settings.- Popup shows a warning if target prefixes are unresolved against known + workbook contexts.
- During preview rendering, compact property keys are expanded using context prefixes so full-URI template columns can resolve merged values.
Root-dataset metadata (name, description, license, etc.) and root-level config
(@type, conformsTo, the metadata descriptor's own license, and which per-file custom
properties get written) come entirely from the selected MASP profile:
@type/conformsTo/metadataLicence/fileProperties from that profile's
mode file, everything else from the values entered on the Describe step (driven
by the profile's schema). There is no built-in fallback and no folder-level config.json
override — a profile must be selected before Build is reachable, so this is always fully
determined. There's no src/defaults.js anymore — the AUSTLANG plugin owns its own
rdf:Property definitions too (added only when it actually identifies a language).
src/crate.jsis isomorphic. It imports only browser-safe entry points and returns bytes/strings; the caller does I/O. That's why it can be unit-tested in Node (test-crate.mjs) yet also run in the browser.ro-crate-excelis imported viaro-crate-excel/lib/workbook.js, not the package index. The index pulls in Node-only modules (shelljs,fs-extra,hasha) for OCFL/bagging;lib/workbook.jsneeds onlyexceljs,ro-crate,lodash,uuid. exceljs ships a browser build (dist/exceljs.min.js) that Vite selects automatically; we write.xlsxviaworkbook.xlsx.writeBuffer()(a Blob) instead of to disk.ro-crate-static-siterenders offline. It uses nunjucks precompiled templates (nofs), and would otherwisefetchits default layout from GitHub at runtime (fragile + CORS). Instead we always pass an explicit layout — the selected profile's ownpropertyGroups, resolved against the built crate's context (src/plugins/ro-crate-html-output/layout.js). There's no bundled generic fallback:crateToPreviewHtml/crateToMultiPageHtml(src/crate.js) throw if no layout is supplied, rather than silently using one.vite.config.jsaddsvite-plugin-node-polyfills(Buffer/process/global) as a safety net for transitive deps, andbase: './'so the built site works from any path.
- PDF content language identification (the original uses
pdf-parse) — only filename-based AUSTLANG matching is ported. - OCFL building (
merge.js,-m/-g). - Formal SHACL-style RO-Crate validation — the
validate-crateplugin runs the selected profile's MASP shape rules (viaro-crate-masp) after each build, which is narrower.