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 @@ -