Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions scripts/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ Generates `website/lib/generated/memberOrganizationsOutput.json`, the list of or
- Walks each group's participations and collects the participating organizations (excluding individual participants).
- Orders groups according to a predefined list and sorts organizations alphabetically.

### `research-papers`

Generates `website/lib/generated/researchPapers.json`, the list of research papers related to the Web of Things.

- Reads the curated paper list (title → DOI) from [research-papers/researchPapersInput.ts](research-papers/researchPapersInput.ts).
- For each DOI, fetches publication metadata from the [Crossref API](https://api.crossref.org/works).

> Unlike `dev-tools` and `member-organizations`, this script is **not** run periodically/automatically. The relevant research papers change infrequently, so the list is maintained by hand and the script is run manually only when new papers need to be added. To add a paper, insert a new entry in [research-papers/researchPapersInput.ts](research-papers/researchPapersInput.ts) and run `npm run research-papers`.

## Setup

```bash
Expand All @@ -48,13 +57,16 @@ npm run dev-tools

# Generate member organizations data
npm run member-organizations

# Generate research papers data (run manually, on demand)
npm run research-papers
```

Both scripts are run via [tsx](https://github.com/privatenumber/tsx), so no build step is required.
The scripts are run via [tsx](https://github.com/privatenumber/tsx), so no build step is required.

## Automation

These scripts run automatically through the [Weekly Script Updates](../.github/workflows/scripts.yml) GitHub workflow (scheduled weekly, or triggered manually via `workflow_dispatch`). The workflow:
The `dev-tools` and `member-organizations` scripts run automatically through the [Weekly Script Updates](../.github/workflows/scripts.yml) GitHub workflow (scheduled weekly, or triggered manually via `workflow_dispatch`). The `research-papers` script is excluded from this automation and is run manually on demand. The workflow:

1. Runs both scripts.
2. Commits directly to `main` if only `lastUpdated` fields changed.
Expand Down
133 changes: 0 additions & 133 deletions scripts/member-organizations.ts

This file was deleted.

3 changes: 2 additions & 1 deletion scripts/member-organizations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ async function fetchMemberOrganizations() {
const result: Result = {};

for (const groupLink of groups._links.groups) {
// Skip Web of Things Interest Group
if (groupLink.href === 'https://api.w3.org/groups/ig/wot') continue;
const groupRes = await fetch(groupLink.href);
const group = (await groupRes.json()) as Group;
let participationsLink: string | undefined = group._links.participations.href;
Expand Down Expand Up @@ -45,7 +47,6 @@ async function fetchMemberOrganizations() {
'Web of Things Community Group',
'Web Thing Protocol Community Group',
'Web of Things Japanese Community Group',
'Web of Things Interest Group',
];

for (const groupName of groupOrder) {
Expand Down
3 changes: 2 additions & 1 deletion scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"type": "module",
"scripts": {
"dev-tools": "npx tsx dev-tools/index.ts",
"member-organizations": "npx tsx member-organizations/index.ts"
"member-organizations": "npx tsx member-organizations/index.ts",
"research-papers": "npx tsx research-papers/index.ts"
},
"devDependencies": {
"@types/node": "^25.2.1",
Expand Down
87 changes: 87 additions & 0 deletions scripts/research-papers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import { RESEARCH_PAPERS } from './researchPapersInput.ts';
import { mkdirSync, writeFileSync } from 'fs';
import { CrossrefDate, ResearchPaperFetchOutput } from './types.ts';

const __dirname = dirname(fileURLToPath(import.meta.url));

// Crossref asks callers to identify themselves; this also grants access to the
// faster "polite" pool and reduces the chance of throttled/empty responses.
const HEADERS = {
'User-Agent': 'wot-marketing/1.0 (https://github.com/w3c/wot-marketing; mailto:bobur.khayitov@siemens.com)',
};

(async () => {
const researchPapers: ResearchPaperFetchOutput[] = [];
// Fetch sequentially to stay well within Crossref's rate limits; firing all
for (const [title, doi] of Object.entries(RESEARCH_PAPERS)) {
try {
const result = await fetchWork(doi);
researchPapers.push(result);
} catch (err) {
console.error(`Could not fetch ${title} \n ${err}`);
}
}
// Save the fetched data in devToolsOutput.json
try {
const outputPath = resolve(__dirname, '../../website/lib/generated/researchPapers.json');
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(outputPath, JSON.stringify(researchPapers, null, 2));
console.log('website/lib/generated/researchPapers.json successfully generated');
} catch (error) {
console.error('Could not save the output file:', error);
}
})();

async function fetchWork(doi: string, retries = 3): Promise<ResearchPaperFetchOutput> {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const response = await fetch(`https://api.crossref.org/works/${doi}`, {
headers: HEADERS,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
// Read the body as text first so an empty/truncated response gives a clear error
const body = await response.text();
if (!body.trim()) {
throw new Error('Empty response body');
}
const message = JSON.parse(body).message;
return {
authors: message.author
.sort((a: any, b: any) => {
if (a.sequence === b.sequence) return 0;
return a.sequence === 'first' ? -1 : 1;
})
.map((author: any) => author.given + ' ' + author.family),
date: crossrefDateToLocaleString(message.published),
link: message.URL,
publisher: message.publisher,
title: message.title.join('. '),
type: message.type.replace(/[._-]+/g, ' ').replace(/\b\w/g, (char: string) => char.toUpperCase()),
};
} catch (err) {
if (attempt === retries) throw err;
// Exponential backoff before retrying (500ms, 1s, 2s, ...).
await sleep(500 * 2 ** attempt);
}
}
throw new Error('Could not fetch: ' + doi);
}

export function crossrefDateToLocaleString(date?: CrossrefDate): string {
const parts = date?.['date-parts']?.[0];

if (!parts?.[0]) throw new Error('Could not extract publish date');

const [year, month = 1, day = 1] = parts;

const dateObj = new Date(Date.UTC(year, month - 1, day));
return dateObj.toISOString();
}

function sleep(ms: number) {
return new Promise((res) => setTimeout(res, ms));
}
35 changes: 35 additions & 0 deletions scripts/research-papers/researchPapersInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export const RESEARCH_PAPERS = {
'Methodologies in digital twin for manufacturing industry: A systematic literature review':
'10.1016/j.future.2025.107997',
'Increasing interoperability between digital twin standards and specifications: transformation of DTDL to AAS':
'10.3390/s23187742',
'Internet of Things: A comprehensive survey on syntactic and semantic interoperability for effective inter-entity communication':
'10.1016/j.array.2026.100942',
'Overview and comparison of asset information model standards': '10.1109/access.2023.3312286',
'DTAG: A methodology for aggregating digital twins using the WoTDT ontology': '10.3390/app14135960',
'Machine component monitoring in data and service ecosystems enabled by the asset administration shell':
'10.1016/j.procir.2024.10.269',
'Decentralized authentication for web of things: a self-sovereign identity (SSI)-based solution':
'10.1109/icnc59896.2024.10556086',
'Cyberlogic: A Foundational Framework for Cross-Space Logic in the Cyber-Physical-Social-Thinking Hyperspace':
'10.1109/jiot.2026.3698940',
'HoloWoT: a first step towards mixed reality digital twins for the industrial internet of things':
'10.1145/3652620.3688243',
'WoT2Pod: an architecture enabling an edge-to-cloud continuum': '10.1145/3627050.3627063',
'Proximity-based service discovery for distributed digital twin systems': '10.1007/s43926-025-00103-x',
'The Arrowhead Framework Ontology': '10.1109/ojies.2026.3693084',
'WoT-Server: Combining the Web-Of-Things and SensorThings standards to build an IoT platform': '10.21105/joss.09470',
'RobWoT: Generating Real-Time Digital Twin Simulations for the Robotic Web of Things':
'10.1109/wf-iot58464.2023.10539381',
'From Fragmentation to Integration: A Framework for Heterogeneous IoT-based Smart City Systems':
'10.1109/etfa65518.2025.11205783',
'RobWoTAR: Web of Things Approach to Universally Control Robots in Augmented Reality': '10.1145/3770501.3770526',
'Skill-Based Multi-Agent Systems for Building Control Using KNX IoT and Web of Things':
'10.1109/etfa65518.2025.11205736',
'Asynchronous Thing Descriptions for Crafting Applications in the Web of Things': '10.1109/ictai66417.2025.00197',
'RetroWoT: A Method to Integrate Brownfield Devices in the Web of Things': '10.1145/3703790.3703799',
'Vendor-Independent Data Platform Architecture in Smart Buildings': '10.1109/ccnc65079.2026.11366376',
'Stateful-WoT: Capturing the Behavior of Highly Dynamic Cyber-Physical Systems': '10.1109/coins61597.2024.10622288',
'Leveraging Information-Centric Edge Computing in Private 5G Cellular Networks via the W3C Web of Things':
'10.1109/ccnc54725.2025.10976001',
};
12 changes: 12 additions & 0 deletions scripts/research-papers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface ResearchPaperFetchOutput {
title: string;
type: string;
publisher: string;
authors: string[];
date: string;
link: string;
}

export type CrossrefDate = {
'date-parts'?: number[][];
};
Loading