Repository: karen-irc/option-t
Pull request #1524 (2023-01-19 22:01:04)

Add unit tests to check package.json's `exports` field's format


Patch:
diff --git a/.github/workflows/_ci.yaml b/.github/workflows/_ci.yaml
index 51fdac86..fb8e907e 100644
--- a/.github/workflows/_ci.yaml
+++ b/.github/workflows/_ci.yaml
@@ -128,11 +128,26 @@ jobs:
         steps:
             - uses: actions/checkout@v3
             - uses: ./.github/actions/prepare_ci
-
             - run: make test_module_path_rewrite -j
               env:
                   CI: true
 
+    test_package_json_exports_field_format:
+        needs: [install_dependencies]
+        runs-on: ubuntu-latest
+        container: node:${{ matrix.node-version }}-bullseye
+
+        strategy:
+            matrix:
+                node-version:
+                    - 18
+        steps:
+            - uses: actions/checkout@v3
+            - uses: ./.github/actions/prepare_ci
+            - run: make test_package_json_exports_field_format -j
+              env:
+                  CI: true
+
     test_package_install:
         needs: [install_dependencies]
         runs-on: ubuntu-latest
diff --git a/Makefile b/Makefile
index 35e9ecd2..f241c7ab 100644
--- a/Makefile
+++ b/Makefile
@@ -208,6 +208,14 @@ test_module_path_rewrite: build
 run_test_module_path_rewrite:
 	$(NODE_BIN) $(CURDIR)/tools/test_path_rewrite.mjs --target $(DIST_DIR)
 
+.PHONY: test_package_json_exports_field_format
+test_package_json_exports_field_format: build
+	$(MAKE) run_test_package_json_exports_field_format -C $(CURDIR)
+
+.PHONY: run_test_package_json_exports_field_format
+run_test_package_json_exports_field_format:
+	TARGET_DIR=$(DIST_DIR) $(NODE_BIN) --test $(CURDIR)/tools/test_package_json_exports_field_format.mjs
+
 .PHONY: test_package_install
 test_package_install: build __run_install_package
 	$(MAKE) run_test_package_install -C $(CURDIR)
@@ -260,6 +268,7 @@ prepublish: ## Run some commands for 'npm run prepublish'
 	$(MAKE) build -C $(CURDIR)
 	$(MAKE) test_distribution_contain_all -C $(CURDIR)
 	$(MAKE) run_test_module_path_rewrite -C $(CURDIR)
+	$(MAKE) run_test_check_whether_exports_filed_file_exist -C $(CURDIR)
 
 .PHONY: publish
 publish: copy_npmrc_to_project_root ## Run some commands for 'npm publish'


Test patch:
diff --git a/tools/test_package_json_exports_field_format.mjs b/tools/test_package_json_exports_field_format.mjs
new file mode 100644
index 00000000..4b4a70e0
--- /dev/null
+++ b/tools/test_package_json_exports_field_format.mjs
@@ -0,0 +1,85 @@
+import * as assert from 'node:assert/strict';
+import * as fs from 'node:fs/promises';
+import * as path from 'node:path';
+import test from 'node:test';
+
+function parseJSON(text) {
+    try {
+        const o = JSON.parse(text);
+        return o;
+    }
+    catch (_e) {
+        return null;
+    }
+}
+
+async function loadJSON(cwd, dir) {
+    const relativePath = path.join(dir, './package.json');
+    const absPath = path.resolve(cwd, relativePath);
+    const content = await fs.readFile(absPath, {
+        encoding: 'utf8',
+        flag: 'r',
+    });
+    const parsed = parseJSON(content);
+    assert.notStrictEqual(parsed, null, `could not load ${absPath}`);
+    return parsed;
+}
+
+function getExportsField(obj) {
+    const exports = obj.exports;
+    assert.strictEqual(typeof exports, 'object', 'exports field must be object');
+    assert.ok(!!exports, 'exports field is nothing');
+    return exports;
+}
+
+function* iterateKeysOfExportsFieldDeeply(obj, prefix) {
+    assert.strictEqual(typeof prefix, 'string');
+
+    for (const [key, value] of Object.entries(obj)) {
+        const route = `${prefix}::${key}`;
+
+        if (typeof value === 'object') {
+            assert.notStrictEqual(value, null, `${route}'s value is null`);
+            yield* iterateKeysOfExportsFieldDeeply(value, route);
+            continue;
+        }
+
+        assert.strictEqual(typeof value, 'string', `${route} is not string`);
+
+        yield Object.freeze({
+            route,
+            key,
+            value,
+        });
+    }
+}
+
+async function checkWhetherFileExist(parentDir, filename) {
+    assert.strictEqual(typeof parentDir, 'string', `should set parentDir`);
+    assert.strictEqual(typeof filename, 'string', `should set filename`);
+
+    const filepath = path.resolve(parentDir, filename);
+    const stat = await fs.stat(filepath);
+    assert.ok(stat.isFile(), `${filename} must be file`);
+}
+
+await test(`Check package.json's 'exports' field format`, async (t) => {
+    const targetDir = process.env.TARGET_DIR;
+    assert.ok(!!targetDir, 'TARGET_DIR envvar should be set');
+
+    const json = await loadJSON(process.cwd(), targetDir);
+    const exports = getExportsField(json);
+
+    await t.test(`This script tests whether the file specied in \`exports\` field in package.json exist`, async (t) => {
+        const assetion = checkWhetherFileExist.bind(null, targetDir);
+
+        const runningTests = [];
+        for (const { route, value } of iterateKeysOfExportsFieldDeeply(exports, '')) {
+            const running = t.test(route, async (_t) => {
+                await assetion(value);
+            });
+            runningTests.push(running);
+        }
+        await Promise.all(runningTests);
+    });
+});