@fedify/vocab-tools 2.0.0-dev.12 → 2.0.0-dev.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ import { join } from "node:path";
2
+ import { readdir } from "node:fs/promises";
3
+
4
+ //#region src/fs.ts
5
+ /**
6
+ * Recursively read a directory, yielding the paths of all files. File paths
7
+ * are relative to the directory, and directories are not yielded.
8
+ * @param dir The directory to read.
9
+ * @returns An async iterable of file paths.
10
+ */
11
+ async function* readDirRecursive(dir) {
12
+ for (const entry of await readdir(dir, { withFileTypes: true })) if (entry.isDirectory()) {
13
+ const path = join(dir, entry.name);
14
+ for await (const subentry of readDirRecursive(path)) yield join(entry.name, subentry);
15
+ } else yield entry.name;
16
+ }
17
+
18
+ //#endregion
19
+ export { readDirRecursive };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,28 @@
1
+ import { readDirRecursive } from "./fs-B12zeNxV.js";
2
+ import { deepStrictEqual } from "node:assert";
3
+ import { join } from "node:path";
4
+ import { test } from "node:test";
5
+ import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
6
+ import { tmpdir } from "node:os";
7
+
8
+ //#region src/fs.test.ts
9
+ test("readDirRecursive()", async () => {
10
+ const dir = await mkdtemp(join(tmpdir(), "fedify-test-"));
11
+ await mkdir(join(dir, "a"));
12
+ await writeFile(join(dir, "a", "aa.txt"), "aa");
13
+ await writeFile(join(dir, "a", "ab.txt"), "aa");
14
+ await mkdir(join(dir, "a", "aa"));
15
+ await writeFile(join(dir, "a", "aa", "aaa.txt"), "aaa");
16
+ await mkdir(join(dir, "b"));
17
+ await writeFile(join(dir, "b", "ba.txt"), "ba");
18
+ await writeFile(join(dir, "b", "bb.txt"), "bb");
19
+ deepStrictEqual(new Set(await Array.fromAsync(readDirRecursive(dir))), new Set([
20
+ join("a", "aa", "aaa.txt"),
21
+ join("a", "aa.txt"),
22
+ join("a", "ab.txt"),
23
+ join("b", "ba.txt"),
24
+ join("b", "bb.txt")
25
+ ]));
26
+ });
27
+
28
+ //#endregion
package/dist/mod.cjs CHANGED
@@ -31,7 +31,7 @@ const es_toolkit = __toESM(require("es-toolkit"));
31
31
 
32
32
  //#region deno.json
33
33
  var name = "@fedify/vocab-tools";
34
- var version = "2.0.0-dev.12+0e4c2ede";
34
+ var version = "2.0.0-dev.158+628cd89e";
35
35
  var license = "MIT";
36
36
  var exports$1 = { ".": "./src/mod.ts" };
37
37
  var author = {
@@ -48,6 +48,7 @@ var exclude = [
48
48
  "node_modules",
49
49
  "src/schema.yaml"
50
50
  ];
51
+ var publish = { "exclude": ["**/*.test.ts"] };
51
52
  var tasks = {
52
53
  "check": "deno fmt --check && deno lint && deno check src/*.ts",
53
54
  "test": "deno test -A"
@@ -60,6 +61,7 @@ var deno_default = {
60
61
  author,
61
62
  imports,
62
63
  exclude,
64
+ publish,
63
65
  tasks
64
66
  };
65
67
 
package/dist/mod.js CHANGED
@@ -8,7 +8,7 @@ import { pascalCase } from "es-toolkit";
8
8
 
9
9
  //#region deno.json
10
10
  var name = "@fedify/vocab-tools";
11
- var version = "2.0.0-dev.12+0e4c2ede";
11
+ var version = "2.0.0-dev.158+628cd89e";
12
12
  var license = "MIT";
13
13
  var exports = { ".": "./src/mod.ts" };
14
14
  var author = {
@@ -25,6 +25,7 @@ var exclude = [
25
25
  "node_modules",
26
26
  "src/schema.yaml"
27
27
  ];
28
+ var publish = { "exclude": ["**/*.test.ts"] };
28
29
  var tasks = {
29
30
  "check": "deno fmt --check && deno lint && deno check src/*.ts",
30
31
  "test": "deno test -A"
@@ -37,6 +38,7 @@ var deno_default = {
37
38
  author,
38
39
  imports,
39
40
  exclude,
41
+ publish,
40
42
  tasks
41
43
  };
42
44
 
@@ -0,0 +1,96 @@
1
+ import { readDirRecursive } from "./fs-B12zeNxV.js";
2
+ import { join, posix } from "node:path";
3
+ import { Validator } from "@cfworker/json-schema";
4
+ import { readFile } from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import { parse } from "yaml";
7
+
8
+ //#region src/schema.ts
9
+ /**
10
+ * Type guard to check if a property is not functional (has pluralName).
11
+ */
12
+ function isNonFunctionalProperty(property) {
13
+ return property.functional !== true;
14
+ }
15
+ /**
16
+ * Type guard to check if a property has singular accessor.
17
+ */
18
+ function hasSingularAccessor(property) {
19
+ if (property.functional === true) return true;
20
+ return isNonFunctionalProperty(property) && property.singularAccessor === true;
21
+ }
22
+ /**
23
+ * An error that occurred while loading a schema file.
24
+ */
25
+ var SchemaError = class extends Error {
26
+ /**
27
+ * The path of the schema file.
28
+ */
29
+ path;
30
+ /**
31
+ * Constructs a new `SchemaError`.
32
+ * @param path The path of the schema file.
33
+ * @param message The error message.
34
+ */
35
+ constructor(path, message) {
36
+ super(message);
37
+ this.path = path;
38
+ }
39
+ };
40
+ async function loadSchemaValidator() {
41
+ const thisFile = import.meta.url;
42
+ const schemaFile = new URL(posix.join(posix.dirname(thisFile), "schema.yaml"));
43
+ let content;
44
+ if (schemaFile.protocol !== "file:") {
45
+ const response = await fetch(schemaFile);
46
+ content = await response.text();
47
+ } else content = await readFile(fileURLToPath(schemaFile), { encoding: "utf-8" });
48
+ const schemaObject = parse(content);
49
+ return new Validator(schemaObject);
50
+ }
51
+ let schemaValidator = void 0;
52
+ async function loadSchema(path) {
53
+ const content = await readFile(path, { encoding: "utf-8" });
54
+ const schema = parse(content);
55
+ if (schemaValidator == null) schemaValidator = await loadSchemaValidator();
56
+ const result = schemaValidator.validate(schema);
57
+ const errors = [];
58
+ if (result.valid) return schema;
59
+ for (const e of result.errors) errors.push(new SchemaError(path, `${path}:${e.instanceLocation}: ${e.error}`));
60
+ throw new AggregateError(errors);
61
+ }
62
+ /**
63
+ * Loads all schema files in the directory.
64
+ * @param dir The path of the directory to load schema files from.
65
+ * @returns A map from the qualified URI of a type to its {@link SchemaFile}.
66
+ * @throws {@link AggregateError} if any schema file is invalid. It contains
67
+ * all {@link SchemaError}s of the invalid schema files.
68
+ */
69
+ async function loadSchemaFiles(dir) {
70
+ if (typeof dir !== "string") throw new TypeError("Expected a directory path in string");
71
+ const result = {};
72
+ const errors = [];
73
+ for await (const relPath of readDirRecursive(dir)) {
74
+ if (!relPath.match(/\.ya?ml$/i)) continue;
75
+ if (relPath.match(/(^|[/\\])schema.yaml$/i)) continue;
76
+ const path = join(dir, relPath);
77
+ let schema;
78
+ try {
79
+ schema = await loadSchema(path);
80
+ } catch (e) {
81
+ if (e instanceof AggregateError && e.errors.length > 0 && e.errors[0] instanceof SchemaError) {
82
+ errors.push(...e.errors);
83
+ continue;
84
+ }
85
+ throw e;
86
+ }
87
+ result[schema.uri] = schema;
88
+ }
89
+ if (errors.length > 0) throw new AggregateError(errors);
90
+ const entries = Object.entries(result);
91
+ entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
92
+ return Object.fromEntries(entries);
93
+ }
94
+
95
+ //#endregion
96
+ export { hasSingularAccessor, isNonFunctionalProperty, loadSchemaFiles };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,140 @@
1
+ import "./fs-B12zeNxV.js";
2
+ import { hasSingularAccessor, isNonFunctionalProperty } from "./schema-s8NE7Bb_.js";
3
+ import { deepStrictEqual, ok } from "node:assert";
4
+ import { test } from "node:test";
5
+
6
+ //#region src/schema.test.ts
7
+ test("isNonFunctionalProperty: returns true for non-functional property", () => {
8
+ const property = {
9
+ singularName: "name",
10
+ pluralName: "names",
11
+ uri: "https://example.com/name",
12
+ description: "A name property",
13
+ range: ["https://example.com/Text"],
14
+ functional: false
15
+ };
16
+ ok(isNonFunctionalProperty(property));
17
+ if (isNonFunctionalProperty(property)) {
18
+ const _pluralName = property.pluralName;
19
+ const _singularAccessor = property.singularAccessor;
20
+ const _container = property.container;
21
+ }
22
+ });
23
+ test("isNonFunctionalProperty: returns true for property without functional field", () => {
24
+ const property = {
25
+ singularName: "name",
26
+ pluralName: "names",
27
+ uri: "https://example.com/name",
28
+ description: "A name property",
29
+ range: ["https://example.com/Text"]
30
+ };
31
+ ok(isNonFunctionalProperty(property));
32
+ });
33
+ test("isNonFunctionalProperty: returns false for functional property", () => {
34
+ const property = {
35
+ singularName: "id",
36
+ uri: "https://example.com/id",
37
+ description: "An ID property",
38
+ range: ["https://example.com/ID"],
39
+ functional: true
40
+ };
41
+ ok(!isNonFunctionalProperty(property));
42
+ });
43
+ test("hasSingularAccessor: returns true for functional property", () => {
44
+ const property = {
45
+ singularName: "id",
46
+ uri: "https://example.com/id",
47
+ description: "An ID property",
48
+ range: ["https://example.com/ID"],
49
+ functional: true
50
+ };
51
+ ok(hasSingularAccessor(property));
52
+ });
53
+ test("hasSingularAccessor: returns true for non-functional property with singularAccessor", () => {
54
+ const property = {
55
+ singularName: "name",
56
+ pluralName: "names",
57
+ uri: "https://example.com/name",
58
+ description: "A name property",
59
+ range: ["https://example.com/Text"],
60
+ functional: false,
61
+ singularAccessor: true
62
+ };
63
+ ok(hasSingularAccessor(property));
64
+ });
65
+ test("hasSingularAccessor: returns false for non-functional property without singularAccessor", () => {
66
+ const property = {
67
+ singularName: "name",
68
+ pluralName: "names",
69
+ uri: "https://example.com/name",
70
+ description: "A name property",
71
+ range: ["https://example.com/Text"],
72
+ functional: false,
73
+ singularAccessor: false
74
+ };
75
+ ok(!hasSingularAccessor(property));
76
+ });
77
+ test("hasSingularAccessor: returns false for non-functional property with undefined singularAccessor", () => {
78
+ const property = {
79
+ singularName: "name",
80
+ pluralName: "names",
81
+ uri: "https://example.com/name",
82
+ description: "A name property",
83
+ range: ["https://example.com/Text"]
84
+ };
85
+ ok(!hasSingularAccessor(property));
86
+ });
87
+ test("Type guard combinations: functional property with redundantProperties", () => {
88
+ const property = {
89
+ singularName: "type",
90
+ uri: "https://www.w3.org/ns/activitystreams#type",
91
+ description: "The type of the object",
92
+ range: ["https://example.com/Type"],
93
+ functional: true,
94
+ redundantProperties: [{ uri: "https://www.w3.org/1999/02/22-rdf-syntax-ns#type" }]
95
+ };
96
+ ok(!isNonFunctionalProperty(property));
97
+ ok(hasSingularAccessor(property));
98
+ });
99
+ test("Type guard combinations: non-functional property with container", () => {
100
+ const property = {
101
+ singularName: "item",
102
+ pluralName: "items",
103
+ uri: "https://example.com/item",
104
+ description: "List of items",
105
+ range: ["https://example.com/Item"],
106
+ functional: false,
107
+ container: "list"
108
+ };
109
+ ok(isNonFunctionalProperty(property));
110
+ ok(!hasSingularAccessor(property));
111
+ if (isNonFunctionalProperty(property)) deepStrictEqual(property.container, "list");
112
+ });
113
+ test("Type guard combinations: non-functional property with graph container", () => {
114
+ const property = {
115
+ singularName: "member",
116
+ pluralName: "members",
117
+ uri: "https://example.com/member",
118
+ description: "Graph of members",
119
+ range: ["https://example.com/Member"],
120
+ functional: false,
121
+ container: "graph"
122
+ };
123
+ ok(isNonFunctionalProperty(property));
124
+ ok(!hasSingularAccessor(property));
125
+ if (isNonFunctionalProperty(property)) deepStrictEqual(property.container, "graph");
126
+ });
127
+ test("Type guard combinations: untyped property", () => {
128
+ const property = {
129
+ singularName: "value",
130
+ pluralName: "values",
131
+ uri: "https://example.com/value",
132
+ description: "Untyped values",
133
+ untyped: true,
134
+ range: ["https://example.com/Value"]
135
+ };
136
+ ok(isNonFunctionalProperty(property));
137
+ ok(!hasSingularAccessor(property));
138
+ });
139
+
140
+ //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-tools",
3
- "version": "2.0.0-dev.12+0e4c2ede",
3
+ "version": "2.0.0-dev.158+628cd89e",
4
4
  "description": "Code generator for Activity Vocabulary APIs",
5
5
  "homepage": "https://fedify.dev/",
6
6
  "repository": {
package/src/class.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { deepStrictEqual } from "node:assert";
2
- import { basename, dirname, join } from "node:path";
2
+ import { basename, dirname, extname, join } from "node:path";
3
3
  import { test } from "node:test";
4
4
  import metadata from "../deno.json" with { type: "json" };
5
5
  import { generateClasses, sortTopologically } from "./class.ts";
@@ -88,7 +88,7 @@ if ("Deno" in globalThis) {
88
88
 
89
89
  async function getEntireCode() {
90
90
  const packagesDir = dirname(dirname(import.meta.dirname!));
91
- const schemaDir = join(packagesDir, "fedify", "src", "vocab");
91
+ const schemaDir = join(packagesDir, "vocab", "src");
92
92
  const types = await loadSchemaFiles(schemaDir);
93
93
  const entireCode = (await Array.fromAsync(generateClasses(types)))
94
94
  .join("")
@@ -106,7 +106,7 @@ async function changeNodeSnapshotPath() {
106
106
  return join(
107
107
  dirname(path),
108
108
  "__snapshots__",
109
- basename(path) + ".node.snap",
109
+ basename(path.replace(extname(path), ".ts")) + ".node.snap",
110
110
  );
111
111
  },
112
112
  );
package/tsdown.config.ts CHANGED
@@ -1,20 +1,55 @@
1
- import { cp } from "node:fs/promises";
2
- import { join } from "node:path";
1
+ import { cp, glob } from "node:fs/promises";
2
+ import { join, sep } from "node:path";
3
3
  import { defineConfig } from "tsdown";
4
4
 
5
- export default defineConfig({
6
- entry: ["src/mod.ts"],
7
- dts: true,
8
- format: ["esm", "cjs"],
9
- platform: "node",
10
- external: [/^node:/],
11
- hooks: {
12
- "build:done": async (ctx) => {
13
- await cp(
14
- join("src", "schema.yaml"),
15
- join(ctx.options.outDir, "schema.yaml"),
16
- { force: true },
17
- );
5
+ export default [
6
+ defineConfig({
7
+ entry: ["src/mod.ts"],
8
+ dts: true,
9
+ format: ["esm", "cjs"],
10
+ platform: "neutral",
11
+ external: [/^node:/],
12
+ hooks: {
13
+ "build:done": async (ctx) => {
14
+ await cp(
15
+ join("src", "schema.yaml"),
16
+ join(ctx.options.outDir, "schema.yaml"),
17
+ { force: true },
18
+ );
19
+ },
18
20
  },
19
- },
20
- });
21
+ }),
22
+ defineConfig({
23
+ entry: (await Array.fromAsync(glob(`src/**/*.test.ts`)))
24
+ .map((f) => f.replace(sep, "/")),
25
+ format: ["esm"],
26
+ platform: "node",
27
+ external: [
28
+ /^node:/,
29
+ /^bun:/,
30
+ ],
31
+ inputOptions: {
32
+ onwarn(warning, defaultHandler) {
33
+ if (
34
+ warning.code === "UNRESOLVED_IMPORT" &&
35
+ warning.id?.endsWith(".test.ts") &&
36
+ warning.exporter &&
37
+ ["bun:test", "@std/testing/snapshot"].includes(warning.exporter)
38
+ ) {
39
+ return;
40
+ }
41
+ defaultHandler(warning);
42
+ },
43
+ },
44
+ hooks: {
45
+ "build:done": async (ctx) => {
46
+ await cp(
47
+ join("src", "__snapshots__"),
48
+ join(ctx.options.outDir, "__snapshots__"),
49
+ { recursive: true },
50
+ );
51
+ },
52
+ },
53
+ }),
54
+ ];
55
+ // cSpell: ignore onwarn