diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..5d47c21c4
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.eleventy.js b/.eleventy.js
index 096e6fb9a..2ce0822f7 100644
--- a/.eleventy.js
+++ b/.eleventy.js
@@ -12,10 +12,12 @@ const drafts = [
'latest'
];
-module.exports = function(eleventyConfig) {
+export default async function(eleventyConfig) {
eleventyConfig.addPassthroughCopy('404.html');
eleventyConfig.addPassthroughCopy('.htaccess');
eleventyConfig.addPassthroughCopy('LICENSE.md');
+ eleventyConfig.addPassthroughCopy('_headers');
+ eleventyConfig.addPassthroughCopy('_redirects');
eleventyConfig.addPassthroughCopy('benchmarks/**/*.{jsonld,nq,md}');
eleventyConfig.addPassthroughCopy('contexts/**/*.{htaccess,html,jsonld}');
eleventyConfig.addPassthroughCopy('contexts/{event,person,place,recipe,remote-context}');
@@ -35,13 +37,13 @@ module.exports = function(eleventyConfig) {
}
eleventyConfig.addPassthroughCopy('static');
eleventyConfig.addPassthroughCopy('test-suite');
- eleventyConfig.addPassthroughCopy('utils');
eleventyConfig.ignores.add('CONTRIBUTING.md');
eleventyConfig.ignores.add('LICENSE.md');
eleventyConfig.ignores.add('README.md');
eleventyConfig.ignores.add('benchmarks/README.md');
eleventyConfig.ignores.add('contexts/person.html');
eleventyConfig.ignores.add('examples');
+ eleventyConfig.ignores.add('images/Makefile');
eleventyConfig.ignores.add('images/README.md');
eleventyConfig.ignores.add('minutes/**/*');
eleventyConfig.ignores.add('ns/json-ld.html');
diff --git a/.gitignore b/.gitignore
index b64c927ed..0d5760310 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
*.sw[op]
.DS_Store
.wrangler
-node_modules
+node_modules/
playground/jsonld.js
-_site
+_site/
+package-lock.json
diff --git a/.htaccess b/.htaccess
deleted file mode 100644
index 84c18f9ff..000000000
--- a/.htaccess
+++ /dev/null
@@ -1,7 +0,0 @@
-
- ForceType application/ld+json
- SetHandler default_handler
- Header set Access-Control-Allow-Origin "*"
-
-
-Redirect 302 /playground-dev /playground
diff --git a/404.html b/404.html
index b9ba71943..49526412b 100644
--- a/404.html
+++ b/404.html
@@ -16,15 +16,15 @@
-
-
-
+
+
+
-
+
@@ -53,10 +53,10 @@
diff --git a/README.md b/README.md
index 34cd6856f..cd9a3bcc7 100644
--- a/README.md
+++ b/README.md
@@ -74,10 +74,12 @@ To develop this website locally:
```sh
# install dependencies
npm i
-# to rebuild on changes and run a server:
-npm run serve
-# to rebuild on changes:
+# to just build the static files to `_site/`
+npm run build
+# to rebuild the files on changes
npm run watch
+# to serve `_site/` with Cloudflare Pages feature support
+npm run pages # visit http://localhost:8788/
```
Additionally, if you want to use or test the playground `http:` proxy, also run
diff --git a/_headers b/_headers
new file mode 100644
index 000000000..8cede675b
--- /dev/null
+++ b/_headers
@@ -0,0 +1,30 @@
+/contexts/event
+ Content-Type: application/ld+json
+ Access-Control-Allow-Origin: "*"
+/contexts/person
+ Content-Type: application/ld+json
+ Access-Control-Allow-Origin: "*"
+/contexts/place
+ Content-Type: application/ld+json
+ Access-Control-Allow-Origin: "*"
+/contexts/recipe
+ Content-Type: application/ld+json
+ Access-Control-Allow-Origin: "*"
+
+# Tests 0009-0011 Add link header
+/test-suite/tests/remote-doc-0009-in.jsonld
+ Link: ; rel="http://www.w3.org/ns/json-ld#context"
+/test-suite/tests/remote-doc-0010-in.json
+ Link: ; rel="http://www.w3.org/ns/json-ld#context"
+/test-suite/tests/remote-doc-0011-in.jldt
+ Link: ; rel="http://www.w3.org/ns/json-ld#context"
+
+# Test 00012 adds multiple link headers
+/test-suite/tests/remote-doc-0012-in.json
+ Link: ; rel="http://www.w3.org/ns/json-ld#context"
+ Link: ; rel="http://www.w3.org/ns/json-ld#context"
+
+/test-suite/tests/*.jldt
+ Content-Type: application/jldTest+json
+/test-suite/tests/*.jldte
+ Content-Type: application/jldTest
diff --git a/_redirects b/_redirects
new file mode 100644
index 000000000..719487ebe
--- /dev/null
+++ b/_redirects
@@ -0,0 +1,20 @@
+/playground-dev /playground 302
+
+/contexts/schema.org.jsonld https://schema.org/ 301
+
+/spec/latest/json-ld-syntax https://www.w3.org/TR/json-ld/ 301
+/spec/latest/json-ld-syntax/ https://www.w3.org/TR/json-ld/ 301
+
+# Tests 0005-0007, status redirect to 0001
+/test-suite/remote-doc-0005-in.jsonld /test-suite/tests/remote-doc-0001-in.jsonld 301
+/test-suite/remote-doc-0006-in.jsonld /test-suite/tests/remote-doc-0001-in.jsonld 303
+/test-suite/remote-doc-0007-in.jsonld /test-suite/tests/remote-doc-0001-in.jsonld 307
+
+/spec/latest/rdf-graph-normalization https://w3c-ccg.github.io/rdf-dataset-canonicalization/spec/ 307
+/spec/latest/rdf-graph-normalization/* https://w3c-ccg.github.io/rdf-dataset-canonicalization/spec/:splat 307
+/spec/latest/rdf-dataset-normalization/* https://w3c-ccg.github.io/rdf-dataset-canonicalization/spec/:splat 307
+/spec/latest/rdf-dataset-canonicalization/* https://w3c-ccg.github.io/rdf-dataset-canonicalization/spec/:splat 307
+
+/spec/latest/json-ld/* https://www.w3.org/TR/json-ld/:splat 301
+/spec/latest/json-ld-api/* https://www.w3.org/TR/json-ld-api/:splat 301
+/spec/latest/json-ld-framing/* https://www.w3.org/TR/json-ld-framing/:splat 301
diff --git a/contexts/.htaccess b/contexts/.htaccess
deleted file mode 100644
index a8f948ec7..000000000
--- a/contexts/.htaccess
+++ /dev/null
@@ -1,7 +0,0 @@
-
- ForceType application/ld+json
- SetHandler default_handler
- Header set Access-Control-Allow-Origin "*"
-
-
-Redirect 301 /contexts/schema.org.jsonld http://schema.org/
diff --git a/functions/test-suite/_middleware.js b/functions/test-suite/_middleware.js
new file mode 100644
index 000000000..bc2db3b83
--- /dev/null
+++ b/functions/test-suite/_middleware.js
@@ -0,0 +1,57 @@
+function parseAcceptHeader(acceptHeader) {
+ // generated by Google AI "parse accept header javascript"
+ if (!acceptHeader) {
+ return [];
+ }
+
+ return acceptHeader.split(',')
+ .map(item => item.trim())
+ .map(item => {
+ const parts = item.split(';');
+ const value = parts[0].trim();
+ let quality = 1;
+
+ if (parts.length > 1) {
+ const q = parts[1].trim();
+ if (q.startsWith('q=')) {
+ quality = parseFloat(q.substring(2));
+ }
+ }
+
+ return { value, quality };
+ })
+ .sort((a, b) => b.quality - a.quality);
+ // From:
+ // text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
+ // Expected output:
+ // [
+ // { value: 'text/html', quality: 1 },
+ // { value: 'application/xhtml+xml', quality: 1 },
+ // { value: 'application/xml', quality: 0.9 },
+ // { value: '*/*', quality: 0.8 }
+ // ]
+}
+
+const typeToExt = {
+ "text/turtle": "ttl",
+ "application/ld+json": "jsonld"
+};
+
+export async function onRequest(context) {
+ try {
+ const parsedAccept = parseAcceptHeader(context.request.headers.get('Accept'));
+ const accept = parsedAccept[0].value;
+
+ // if we have a mapping for this extension, send it. Otherwise fall through.
+ if(Object.keys(typeToExt).indexOf(accept) > -1) {
+ const ext = typeToExt[accept] || `html`;
+ const rewrittenUrl = context.request.url + '.' + ext;
+ const asset = await context.env.ASSETS.fetch(rewrittenUrl);
+ const response = new Response(asset.body, asset);
+ return response;
+ }
+ return context.next();
+ } catch (err) {
+ return new Response(`${err.message}\n${err.stack}`, { status: 500 });
+ }
+}
diff --git a/images/.htaccess b/images/.htaccess
deleted file mode 100644
index ee8e4d25e..000000000
--- a/images/.htaccess
+++ /dev/null
@@ -1,10 +0,0 @@
-
- order allow,deny
- deny from all
-
-
-
- order allow,deny
- deny from all
-
-
diff --git a/package.json b/package.json
index fc7e4bbd4..a1e31927a 100644
--- a/package.json
+++ b/package.json
@@ -2,14 +2,16 @@
"name": "json-ld.org",
"private": true,
"description": "json-ld.org homepage",
+ "type": "module",
"scripts": {
"build": "eleventy",
- "serve": "eleventy --serve",
+ "pages": "wrangler pages dev _site/ --compatibility-date=2025-04-02",
"watch": "eleventy --watch",
"dev": "wrangler pages dev _site --compatibility-date=2024-04-27 --live-reload --port 8788",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
- "@11ty/eleventy": "^3.0.0-alpha.9"
+ "@11ty/eleventy": "^3.0.0",
+ "wrangler": "^4.6.0"
}
}
diff --git a/requirements/index.11tydata.js b/requirements/index.11tydata.js
index 5d9597f64..a3650db65 100644
--- a/requirements/index.11tydata.js
+++ b/requirements/index.11tydata.js
@@ -1,5 +1,8 @@
-const fs = require('node:fs/promises');
-const path = require('node:path');
+import { fileURLToPath } from 'url';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
const specStatuses = ['ED'];
@@ -35,7 +38,7 @@ const specs = [
'requirements'
];
-module.exports = async function() {
+export default async function() {
return {
specs: Object.fromEntries(await Promise.all(specs.map(async spec => {
return [spec, await getDrafts(spec)];
diff --git a/spec/ED/.htaccess b/spec/ED/.htaccess
deleted file mode 100644
index 3b8be4f70..000000000
--- a/spec/ED/.htaccess
+++ /dev/null
@@ -1,3 +0,0 @@
-RewriteEngine on
-RewriteBase /json-ld.org/spec/ED
-RewriteRule ^(20.*) json-ld-syntax/$1 [R=301,L]
diff --git a/spec/index.11tydata.js b/spec/index.11tydata.js
index 83967fda0..a54ae4710 100644
--- a/spec/index.11tydata.js
+++ b/spec/index.11tydata.js
@@ -1,5 +1,8 @@
-const fs = require('node:fs/promises');
-const path = require('node:path');
+import { fileURLToPath } from 'url';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
const specStatuses = ['ED', 'FCGS', 'WD', 'CR', 'PR', 'REC'/*, 'CG-FINAL'*/];
@@ -33,14 +36,14 @@ async function getDrafts(spec) {
const specs = [
'json-ld',
- 'json-ld-syntax',
- 'json-ld-api',
- 'json-ld-api-best-practices',
- 'json-ld-framing',
+ 'json-ld-syntax',
+ 'json-ld-api',
+ 'json-ld-api-best-practices',
+ 'json-ld-framing',
'json-ld-rdf'
];
-module.exports = async function() {
+export default async function() {
return {
specs: Object.fromEntries(await Promise.all(specs.map(async spec => {
return [spec, await getDrafts(spec)];
diff --git a/spec/latest/.htaccess b/spec/latest/.htaccess
deleted file mode 100644
index af6788fc5..000000000
--- a/spec/latest/.htaccess
+++ /dev/null
@@ -1,12 +0,0 @@
-RewriteEngine On
-
-RewriteBase /spec/latest/
-RewriteRule ^json-ld-syntax(.*) json-ld$1 [R=301,NC,L]
-
-RewriteRule ^json-ld/(.*) https://www.w3.org/TR/json-ld/$1 [R=301,NC,L]
-RewriteRule ^json-ld-api/(.*) https://www.w3.org/TR/json-ld-api/$1 [R=301,NC,L]
-RewriteRule ^json-ld-framing/(.*) https://www.w3.org/TR/json-ld-framing/$1 [R=301,NC,L]
-
-RewriteRule ^rdf-graph-normalization(.*) https://w3c-ccg.github.io/rdf-dataset-canonicalization/spec/$1 [R=307,NC,L]
-RewriteRule ^rdf-dataset-normalization/(.*) https://w3c-ccg.github.io/rdf-dataset-canonicalization/spec/$1 [R=307,NC,L]
-RewriteRule ^rdf-dataset-canonicalization/(.*) https://w3c-ccg.github.io/rdf-dataset-canonicalization/spec/$1 [R=307,NC,L]
diff --git a/test-redirects.js b/test-redirects.js
new file mode 100644
index 000000000..f0775ed05
--- /dev/null
+++ b/test-redirects.js
@@ -0,0 +1,116 @@
+import assert from 'node:assert/strict';
+import fs from 'node:fs/promises';
+
+const baseUri = 'http://localhost:8788';
+
+// Test _headers
+(async () => {
+ // test json-ld media type
+ const url = `${baseUri}/contexts/event.jsonld`;
+ const resp = await fetch(url);
+ assert(resp.headers.get('Content-Type') === 'application/ld+json',
+ `Content-Type for ${url} should be application/ld+json.`);
+})();
+
+(async () => {
+ // test json-ld media type on URLs without extensions
+ const urls = [
+ `${baseUri}/contexts/event`,
+ `${baseUri}/contexts/person`,
+ `${baseUri}/contexts/place`,
+ `${baseUri}/contexts/recipe`,
+ ];
+ Promise.all(urls.map(async (url) => {
+ const resp = await fetch(url, {redirect: 'manual'});
+ const contentType = resp.headers.get('Content-Type');
+ assert(contentType === 'application/ld+json',
+ `Content-Type for ${url} should be application/ld+json; got ${contentType}.`);
+ const cors = resp.headers.get('Access-Control-Allow-Origin');
+ assert(cors === '"*"',
+ `Header 'Access-Control-Allow-Origin' should be '*'; got ${cors}.`);
+ }));
+})();
+
+(async () => {
+ // test json-ld media type
+ const urls = [
+ `${baseUri}/contexts/event`,
+ `${baseUri}/contexts/person`,
+ `${baseUri}/contexts/place`,
+ `${baseUri}/contexts/recipe`,
+ ];
+ Promise.all(urls.map(async (url) => {
+ const resp = await fetch(url, {redirect: 'manual'});
+ const contentType = resp.headers.get('Content-Type');
+ assert(contentType === 'application/ld+json',
+ `Content-Type for ${url} should be application/ld+json; got ${contentType}.`);
+ const cors = resp.headers.get('Access-Control-Allow-Origin');
+ assert(cors === '"*"',
+ `Header 'Access-Control-Allow-Origin' should be '*'; got ${cors}.`);
+ }));
+})();
+
+(async () => {
+ // test media type for `*.jldte`
+ const url = `${baseUri}/test-suite/tests/remote-doc-0003-in.jldt`;
+ const resp = await fetch(url);
+ const contentType = resp.headers.get('Content-Type');
+ assert(contentType === 'application/jldTest+json',
+ `Content-Type for ${url} should be application/jldTest+json; got ${contentType}.`);
+})();
+
+(async () => {
+ // test media type for `*.jldte`
+ const url = `${baseUri}/test-suite/tests/remote-doc-0004-in.jldte`;
+ const resp = await fetch(url);
+ const contentType = resp.headers.get('Content-Type');
+ assert(contentType === 'application/jldTest',
+ `Content-Type for ${url} should be application/jldTest; got ${contentType}.`);
+})();
+
+(async () => {
+ // test link headers
+ const urlsToLinkHeaderValues = {
+ '/test-suite/tests/remote-doc-0009-in.jsonld':
+ `; rel="http://www.w3.org/ns/json-ld#context"`,
+ '/test-suite/tests/remote-doc-0010-in.json':
+ `; rel="http://www.w3.org/ns/json-ld#context"`,
+ '/test-suite/tests/remote-doc-0011-in.jldt':
+ `; rel="http://www.w3.org/ns/json-ld#context"`,
+ '/test-suite/tests/remote-doc-0012-in.json':
+ `; rel="http://www.w3.org/ns/json-ld#context", ; rel="http://www.w3.org/ns/json-ld#context"`
+ };
+ Promise.all(Object.entries(urlsToLinkHeaderValues)
+ .map(async ([absoultePath, intendedHeaderValue]) => {
+ const url = `${baseUri}${absoultePath}`;
+ const resp = await fetch(url);
+ const actualLinkValue = resp.headers.get('Link');
+ assert(actualLinkValue === intendedHeaderValue,
+ `Link header for ${url} should be ${intendedHeaderValue}; got ${actualLinkValue}.`);
+ })
+ );
+})();
+
+// Test _redirects
+(async () => {
+ const _redirects = await fs.readFile('./_redirects', 'utf-8');
+ Promise.all(_redirects.split('\n')
+ .filter((v) => (v[0] !== '#') ? v : null)
+ .map(async (line) => {
+ let [source, destination, code] = line.split(/\s/);
+ if(source.endsWith('*')) {
+ // remove *
+ source = source.slice(0, source.length - 1);
+ // remove :splat
+ destination = destination.slice(0, destination.length - 6);
+ }
+ const url = `${baseUri}${source}`;
+ const resp = await fetch(url, {redirect: 'manual'});
+ assert(resp.status === code || 302,
+ `Should be a 302 redirect.`);
+ const location = resp.headers.get('Location');
+ assert(location === destination,
+ `Old ${source} should redirect to ${destination}, got ${location}`);
+ })
+ );
+})();
diff --git a/test-suite/.htaccess b/test-suite/.htaccess
deleted file mode 100644
index 9ca4fb57e..000000000
--- a/test-suite/.htaccess
+++ /dev/null
@@ -1,38 +0,0 @@
-
- ForceType application/ld+json
- SetHandler default_handler
- Header set Access-Control-Allow-Origin "*"
-
-
-# Turn off MultiViews
-Options -MultiViews
-
-# Directive to ensure *.ttl and .jsonld files served appropriately
-AddType text/turtle .ttl
-AddType application/ld+json .jsonld
-AddType application/n-quads .nq
-
-# Rewrite engine setup
-RewriteEngine On
-RewriteBase /test-suite
-
-# Rewrite rule to serve HTML content from the vocabulary URI if requested
-RewriteCond %{HTTP_ACCEPT} !application/rdf\+xml.*(text/html|application/xhtml\+xml)
-RewriteCond %{HTTP_ACCEPT} text/html [OR]
-RewriteCond %{HTTP_ACCEPT} application/xhtml\+xml [OR]
-RewriteCond %{HTTP_USER_AGENT} ^Mozilla/.*
-RewriteRule ^vocab$ vocab.html [R=303]
-
-# Rewrite rule to serve Turtle content from the vocabulary URI if requested
-RewriteCond %{HTTP_ACCEPT} text/turtle
-RewriteRule ^vocab$ vocab.ttl [R=303]
-
-# Rewrite rule to serve JSON-LD content from the vocabulary URI if requested
-RewriteCond %{HTTP_ACCEPT} application/ld+json
-RewriteRule ^vocab$ vocab.jsonld [R=303]
-
-# Choose the default response
-# ---------------------------
-
-# Rewrite rule to serve the HTML content from the vocabulary URI by default
-RewriteRule ^vocab$ vocab.html [R=303]
diff --git a/test-suite/tests/.htaccess b/test-suite/tests/.htaccess
deleted file mode 100644
index 45a04ae85..000000000
--- a/test-suite/tests/.htaccess
+++ /dev/null
@@ -1,30 +0,0 @@
-# Special rules for document loader tests
-# Rewrite engine setup
-RewriteEngine On
-RewriteBase /test-suite
-
-# Add directive for test types
-AddType application/jldTest+json .jldt
-AddType application/jldTest .jldte
-
-# Tests 0005-0007, status redirect to 0001
-RewriteRule ^remote-doc-0005-in.jsonld$ tests/remote-doc-0001-in.jsonld [R=301]
-RewriteRule ^remote-doc-0006-in.jsonld$ tests/remote-doc-0001-in.jsonld [R=303]
-RewriteRule ^remote-doc-0007-in.jsonld$ tests/remote-doc-0001-in.jsonld [R=307]
-
-# Tests 0009-0011 Add link header
-
- Header set Link '; rel="http://www.w3.org/ns/json-ld#context"'
-
-
- Header set Link '; rel="http://www.w3.org/ns/json-ld#context"'
-
-
- Header set Link '; rel="http://www.w3.org/ns/json-ld#context"'
-
-
-# Test 00012 adds multiple link headers
-
- Header set Link '; rel="http://www.w3.org/ns/json-ld#context"'
- Header append Link '; rel="http://www.w3.org/ns/json-ld#context"'
-
diff --git a/utils/.htaccess b/utils/.htaccess
deleted file mode 100644
index d5d0d53bd..000000000
--- a/utils/.htaccess
+++ /dev/null
@@ -1,5 +0,0 @@
-
- order allow,deny
- deny from all
-
-
diff --git a/utils/README b/utils/README
deleted file mode 100644
index 8bb70691c..000000000
--- a/utils/README
+++ /dev/null
@@ -1,13 +0,0 @@
-Usage
------
-
-In order to use the git.php file, you must create a file called
-remote-update-token.txt and place a value in there. You must then call
-the git.php file with a URL parameter named 'token' set to the value in
-the file. For example:
-
-https://json-ld.org/utils/git.php?token=7384724849
-
-While this is not a fool-proof security solution, it'll be good enough
-for now. Updates are throttled, even in the event of a DDoS, the update
-rate is once every 5 seconds.
diff --git a/utils/git.php b/utils/git.php
deleted file mode 100644
index 97296748b..000000000
--- a/utils/git.php
+++ /dev/null
@@ -1,74 +0,0 @@
-