Skip to content

Commit

Permalink
Refactor build process from Python to JavaScript (#78)
Browse files Browse the repository at this point in the history
- Replace Python build script with JavaScript
  - The `選項` argument of `from字頭` now accepts both objects and arrays. (Allowing specifying objects is purely for backward compatibility.) Results are outputted as both array elements and named properties. Closes #77.
- Check coverage during building and testing (Fixes #71)
- Separate type definitions to the dedicated file such that they are recognised when called in a TypeScript file
- Move generated artifacts to a `dist` directory and make the npm package include only this directory

---

* Refactor: Replace Python build script with JS
- Check coverage during building & testing
- Generate & Bundle type definition file; Move JSDoc to there
- Update workflow

* Move artifacts into `dist` directory

* Only include `dist` directory in the npm package

* Fix incorrect type definition

* Exclude `dist` directory from being linted
- `index.js` artifact is now located in `dist`.
- Files to ignore are now specified by CLI flag instead of inside config to get rid of `Unused eslint-disable directive` warning in `index.js`.

* Fix: `dist` directory isn’t created automatically

* Fix indentation

* Fix ignore pattern

---------

Co-authored-by: Sêkai Zhou <[email protected]>
  • Loading branch information
graphemecluster and syimyuzya authored Dec 5, 2024
1 parent cdd9e9d commit 4b4441d
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 87 deletions.
17 changes: 7 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,21 @@ jobs:
- name: Checkout latest code
uses: actions/checkout@v4

# Build
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Build
run: python build.py > index.js

# Test
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: https://registry.npmjs.org/

- name: Install Node.js dependencies
run: npm ci
- name: Lint

- name: Lint schemata
run: npm run lint

- name: Build project
run: npm run build

- name: Run tests
run: npm test

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
node_modules
/index.js
dist
3 changes: 0 additions & 3 deletions .npmignore

This file was deleted.

65 changes: 0 additions & 65 deletions build.py

This file was deleted.

90 changes: 90 additions & 0 deletions build/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';

mkdirSync('./dist', { recursive: true });

writeFileSync('./dist/index.js', /* js */ `\
import TshetUinh from 'tshet-uinh';
import { 推導方案 } from 'tshet-uinh-deriver-tools';
export function from字頭(schema, 字頭, 選項) {
return TshetUinh.資料.query字頭(字頭).map(條目 => {
if (Array.isArray(schema)) (條目.推導結果 = schema.map((schema, i) => _schemata[schema](Array.isArray(選項) && 選項[i] || 選項?.[schema])(條目.音韻地位, 條目.字頭))).forEach((推導結果, i) => 條目.推導結果[schema[i]] = 推導結果);
else 條目.推導結果 = _schemata[schema](選項)(條目.音韻地位, 條目.字頭);
return 條目;
});
}
`);

writeFileSync('./dist/index.d.ts', /* ts */`\
import TshetUinh from 'tshet-uinh';
import { 推導方案 } from 'tshet-uinh-deriver-tools';
type 推導選項 = Readonly<Record<string, unknown>> | undefined;
interface 字頭檢索及推導結果<T extends Schema | readonly Schema[]> extends TshetUinh.資料.檢索結果 {
推導結果: T extends readonly Schema[] ? { -readonly [K in keyof T]: string } & { -readonly [K in T[number]]: string } : string;
}
/**
* 查詢字頭的擬音。
* @param schema - 推導方案或推導方案陣列
* @param 字頭 - 要查詢的字頭
* @param 選項 - 選項(可選)
* - 若 \`schema\` 為字串(單個方案),則該引數為其選項
* - 若 \`schema\` 為字串列表(多個方案),則該引數亦為列表,元素為相應方案的選項
* @return 由字頭檢索到的各條目及相應推導結果
* - 若 \`schema\` 為字串,傳回結果中的 \`推導結果\` 屬性為字串
* - 若 \`schema\` 為字串列表,傳回結果中的 \`推導結果\` 屬性亦為字串列表
*/
export function from字頭<const T extends Schema | readonly Schema[]>(
schema: T,
字頭: string,
選項?: T extends readonly Schema[] ? { readonly [K in keyof T]?: 推導選項 } | { readonly [K in T[number]]?: 推導選項 } : 推導選項,
): 字頭檢索及推導結果<T>[];
`);

const directoryFiles = new Set(readdirSync('.').filter(file => file.endsWith('.js') && !file.endsWith('.config.js')));
const nonExistentFiles = new Set();

const readmeContent = readFileSync('README.md', 'utf-8');
const files = readmeContent.matchAll(/`(([a-z0-9_]+)\.js)`/g);
const schemata = [];

for (const [, file, schema] of files) {
if (!existsSync(file)) {
nonExistentFiles.add(file);
continue;
}

const content = readFileSync(file, 'utf-8');
const [, description, code] = content.match(/^\/\*(.+?)\*\/(.+)$/s);

appendFileSync('./dist/index.js', /* js */ `
export const ${schema} = new 推導方案(function (選項, 音韻地位, 字頭) {
${code.trim()}
});
`);

appendFileSync('./dist/index.d.ts', /* ts */ `
/**
* ${description.trim()}
*/
export const ${schema}: 推導方案<string>;
`);

schemata.push(schema);
directoryFiles.delete(file);
}

appendFileSync('./dist/index.js', /* js */ `
const _schemata = { ${schemata.join(', ')} };
`);

appendFileSync('./dist/index.d.ts', /* ts */ `
type Schema = '${schemata.join("' | '")}';
`);

if (directoryFiles.size || nonExistentFiles.size) {
if (directoryFiles.size) console.error('The following files are missing from README.md:', directoryFiles);
if (nonExistentFiles.size) console.error('The following files are listed in README.md but do not exist:', nonExistentFiles);
process.exit(1);
}
1 change: 0 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import taggedIs from './rules/tagged-is.js';

export default [
{
ignores: ['index.js'],
languageOptions: {
sourceType: 'script',
globals: {
Expand Down
15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@
"version": "20241008.0.0",
"description": "JavaScript code examples to generate the derivatives of the Qieyun phonological system using qieyun-js",
"type": "module",
"main": "index.js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"default": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": ["dist"],
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"lint": "eslint . --ignore-pattern dist/**/*",
"lint:fix": "eslint --fix . --ignore-pattern dist/**/*",
"build": "node build/main.js",
"test": "node test/main.js"
},
"repository": {
Expand Down
23 changes: 19 additions & 4 deletions test/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { readdirSync } from 'node:fs';
import util from 'node:util';

import { 資料, 音韻地位 } from 'tshet-uinh';
import * as TshetUinhExamples from '../index.js';
import * as TshetUinhExamples from '../dist/index.js';

const 地位 = 音韻地位.from描述('書開三宵上');

Expand All @@ -25,17 +26,25 @@ const testCases = [
['yec_en_hua', 'A'],
];

const directorySchemata = new Set(readdirSync('.').flatMap(file => file !== (file = file.replace(/\.js$/, '')) && !file.endsWith('.config') ? [file] : []));
const nonExistentSchemata = new Set();

let passed = 0;
let total = 0;
for (const [schema, expected] of testCases) {
if (!(schema in TshetUinhExamples)) {
nonExistentSchemata.add(schema);
continue;
}

console.log(`Testing: ${schema}`);
total++;
try {
const deriver = TshetUinhExamples[schema]();
Array.from(資料.iter音韻地位(), deriver); // Ensure no error is thrown from all 音韻地位
const result = deriver(地位);
if (result === expected) {
passed += 1;
passed++;
} else {
console.log(
` Expected ${util.inspect(expected)}, got ${util.inspect(result)}`
Expand All @@ -44,7 +53,13 @@ for (const [schema, expected] of testCases) {
} catch (e) {
console.log(` Error thrown: ${util.inspect(e)}`);
}

directorySchemata.delete(schema);
}

console.log(`${passed}/${total} test(s) passed.`);
process.exit(passed === total ? 0 : 1);
console.log(`${passed}/${total} tests passed.`);
if (directorySchemata.size || nonExistentSchemata.size || passed < total) {
if (directorySchemata.size) console.log('The following schemata are untested:', directorySchemata);
if (nonExistentSchemata.size) console.log('There are test cases for the following schemata but they are missing:', nonExistentSchemata);
process.exit(1);
}

0 comments on commit 4b4441d

Please sign in to comment.