@saykit/config 0.3.0 → 0.4.1
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.
- package/README.md +4 -5
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/commands/index.cjs +18 -11
- package/dist/commands/index.mjs +18 -11
- package/dist/features/catalogue/index.cjs +10 -5
- package/dist/features/catalogue/index.d.cts +11 -2
- package/dist/features/catalogue/index.d.mts +11 -2
- package/dist/features/catalogue/index.mjs +7 -3
- package/dist/features/loader/index.cjs +1 -1
- package/dist/features/loader/index.mjs +1 -1
- package/dist/features/messages/index.cjs +1 -1
- package/dist/{hash-8It7TJpB.cjs → hash-CxBlj5Dz.cjs} +1 -1
- package/dist/index.cjs +3 -25
- package/dist/loader-BuAqYtfl.mjs +191 -0
- package/dist/loader-DATEQsXj.cjs +198 -0
- package/dist/{storage-C9nzxPtZ.mjs → storage-CqKPYfCR.mjs} +19 -19
- package/dist/{storage-3BTY8yws.cjs → storage-wS6e6qWM.cjs} +29 -23
- package/package.json +1 -1
- package/dist/loader-B25O-vF6.cjs +0 -121
- package/dist/loader-C2eolE_1.mjs +0 -115
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ export default defineConfig({
|
|
|
43
43
|
```sh
|
|
44
44
|
saykit extract # extract messages once
|
|
45
45
|
saykit extract --watch # extract and watch for changes
|
|
46
|
-
saykit clean #
|
|
46
|
+
saykit clean # prune dead entries from other locales
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Extraction only writes the **source** locale (the first entry in `locales`). Locales that don't
|
|
@@ -54,10 +54,9 @@ At load time (via the unplugin or Babel plugin), each locale module is filled fr
|
|
|
54
54
|
chain, so an untranslated key resolves to a fallback locale and ultimately the source string — the
|
|
55
55
|
runtime still loads a single locale.
|
|
56
56
|
|
|
57
|
-
`saykit clean`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
rather than leaving it to your TMS.
|
|
57
|
+
`saykit clean` only ever subtracts from non-source locales: it drops entries that no longer exist in
|
|
58
|
+
the source catalogue and entries with an empty translation, and leaves everything else exactly as it
|
|
59
|
+
is. It never adds source keys to a locale file, that stays your TMS's job.
|
|
61
60
|
|
|
62
61
|
## Documentation
|
|
63
62
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
Object.defineProperty(exports, "__toESM", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function() {
|
|
26
|
+
return __toESM;
|
|
27
|
+
}
|
|
28
|
+
});
|
package/dist/commands/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
require("../
|
|
3
|
-
const require_storage = require("../storage-
|
|
4
|
-
const require_loader = require("../loader-
|
|
2
|
+
require("../chunk-CKQMccvm.cjs");
|
|
3
|
+
const require_storage = require("../storage-wS6e6qWM.cjs");
|
|
4
|
+
const require_loader = require("../loader-DATEQsXj.cjs");
|
|
5
5
|
let _commander_js_extra_typings = require("@commander-js/extra-typings");
|
|
6
6
|
let node_fs_promises = require("node:fs/promises");
|
|
7
7
|
let node_path = require("node:path");
|
|
@@ -46,17 +46,20 @@ var Logger = class {
|
|
|
46
46
|
new node_async_hooks.AsyncLocalStorage({ defaultValue: new Logger() });
|
|
47
47
|
//#endregion
|
|
48
48
|
//#region src/commands/clean.ts
|
|
49
|
-
var clean_default = new _commander_js_extra_typings.Command("clean").description("
|
|
49
|
+
var clean_default = new _commander_js_extra_typings.Command("clean").description("Remove orphaned and untranslated entries from non-source locale files").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
|
|
50
50
|
const config = require_loader.resolveConfig();
|
|
51
51
|
const logger = new Logger(options);
|
|
52
52
|
logger.header("🧹 Cleaning Locales");
|
|
53
53
|
const [sourceLocale, ...otherLocales] = config.locales;
|
|
54
54
|
for (const bucket of config.buckets) {
|
|
55
55
|
const sourceMessages = await require_storage.readCatalogueMessages(bucket, sourceLocale);
|
|
56
|
-
logger.info(`
|
|
56
|
+
logger.info(`Cleaning ${otherLocales.length} locale(s) against ${sourceLocale}`);
|
|
57
57
|
for (const locale of otherLocales) {
|
|
58
|
-
logger.step(`
|
|
59
|
-
|
|
58
|
+
logger.step(`Cleaning ${locale}`);
|
|
59
|
+
const existingMessages = await require_storage.readCatalogueMessages(bucket, locale);
|
|
60
|
+
const prunedMessages = require_storage.pruneLocaleMessages(existingMessages, sourceMessages);
|
|
61
|
+
logger.step(`Removed ${existingMessages.length - prunedMessages.length} entries from ${locale}`);
|
|
62
|
+
await require_storage.writeCatalogueMessages(bucket, locale, prunedMessages);
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
logger.success("Locales cleaned");
|
|
@@ -76,13 +79,17 @@ async function extractMessagesFromFile(path, bucket) {
|
|
|
76
79
|
//#region src/features/watch.ts
|
|
77
80
|
/**
|
|
78
81
|
* Expand a buckets include and exclude patterns into a flat list of file paths.
|
|
82
|
+
*
|
|
83
|
+
* We stat each match instead of using `glob`'s `withFileTypes` option, which
|
|
84
|
+
* Bun's `node:fs/promises` compatibility layer does not yet support.
|
|
79
85
|
*/
|
|
80
86
|
async function globBucket(bucket) {
|
|
81
87
|
const paths = [];
|
|
82
|
-
for await (const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
for await (const path of (0, node_fs_promises.glob)(bucket.include, { exclude: bucket.exclude })) try {
|
|
89
|
+
if ((await (0, node_fs_promises.stat)(path)).isFile()) paths.push(path);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error.code !== "ENOENT") throw error;
|
|
92
|
+
}
|
|
86
93
|
return paths;
|
|
87
94
|
}
|
|
88
95
|
/**
|
package/dist/commands/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
3
|
-
import { t as resolveConfig } from "../loader-
|
|
2
|
+
import { a as mergeExtractedMessages, i as expandBucketOutputPath, n as writeCatalogueMessages, o as pruneLocaleMessages, t as readCatalogueMessages } from "../storage-CqKPYfCR.mjs";
|
|
3
|
+
import { t as resolveConfig } from "../loader-BuAqYtfl.mjs";
|
|
4
4
|
import { Command, program } from "@commander-js/extra-typings";
|
|
5
|
-
import { access, glob, readFile, watch } from "node:fs/promises";
|
|
5
|
+
import { access, glob, readFile, stat, watch } from "node:fs/promises";
|
|
6
6
|
import { join, relative } from "node:path";
|
|
7
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
8
|
//#region src/features/logger.ts
|
|
@@ -45,17 +45,20 @@ var Logger = class {
|
|
|
45
45
|
new AsyncLocalStorage({ defaultValue: new Logger() });
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/commands/clean.ts
|
|
48
|
-
var clean_default = new Command("clean").description("
|
|
48
|
+
var clean_default = new Command("clean").description("Remove orphaned and untranslated entries from non-source locale files").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
|
|
49
49
|
const config = resolveConfig();
|
|
50
50
|
const logger = new Logger(options);
|
|
51
51
|
logger.header("🧹 Cleaning Locales");
|
|
52
52
|
const [sourceLocale, ...otherLocales] = config.locales;
|
|
53
53
|
for (const bucket of config.buckets) {
|
|
54
54
|
const sourceMessages = await readCatalogueMessages(bucket, sourceLocale);
|
|
55
|
-
logger.info(`
|
|
55
|
+
logger.info(`Cleaning ${otherLocales.length} locale(s) against ${sourceLocale}`);
|
|
56
56
|
for (const locale of otherLocales) {
|
|
57
|
-
logger.step(`
|
|
58
|
-
|
|
57
|
+
logger.step(`Cleaning ${locale}`);
|
|
58
|
+
const existingMessages = await readCatalogueMessages(bucket, locale);
|
|
59
|
+
const prunedMessages = pruneLocaleMessages(existingMessages, sourceMessages);
|
|
60
|
+
logger.step(`Removed ${existingMessages.length - prunedMessages.length} entries from ${locale}`);
|
|
61
|
+
await writeCatalogueMessages(bucket, locale, prunedMessages);
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
logger.success("Locales cleaned");
|
|
@@ -75,13 +78,17 @@ async function extractMessagesFromFile(path, bucket) {
|
|
|
75
78
|
//#region src/features/watch.ts
|
|
76
79
|
/**
|
|
77
80
|
* Expand a buckets include and exclude patterns into a flat list of file paths.
|
|
81
|
+
*
|
|
82
|
+
* We stat each match instead of using `glob`'s `withFileTypes` option, which
|
|
83
|
+
* Bun's `node:fs/promises` compatibility layer does not yet support.
|
|
78
84
|
*/
|
|
79
85
|
async function globBucket(bucket) {
|
|
80
86
|
const paths = [];
|
|
81
|
-
for await (const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
for await (const path of glob(bucket.include, { exclude: bucket.exclude })) try {
|
|
88
|
+
if ((await stat(path)).isFile()) paths.push(path);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error.code !== "ENOENT") throw error;
|
|
91
|
+
}
|
|
85
92
|
return paths;
|
|
86
93
|
}
|
|
87
94
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
require("../../
|
|
3
|
-
const require_hash = require("../../hash-
|
|
4
|
-
const require_storage = require("../../storage-
|
|
2
|
+
require("../../chunk-CKQMccvm.cjs");
|
|
3
|
+
const require_hash = require("../../hash-CxBlj5Dz.cjs");
|
|
4
|
+
const require_storage = require("../../storage-wS6e6qWM.cjs");
|
|
5
5
|
let node_path = require("node:path");
|
|
6
6
|
//#region src/features/catalogue/record.ts
|
|
7
7
|
/**
|
|
@@ -44,17 +44,22 @@ function assembleCatalogueRecord(bucket, contents) {
|
|
|
44
44
|
if (!content) continue;
|
|
45
45
|
for (const message of bucket.formatter.parse(content)) {
|
|
46
46
|
const key = message.id || require_hash.generateHash(message.message, message.context);
|
|
47
|
-
|
|
47
|
+
if (message.translation) {
|
|
48
|
+
record[key] = message.translation;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!record[key] && message.message) record[key] = message.message;
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
return record;
|
|
51
55
|
}
|
|
52
56
|
//#endregion
|
|
53
57
|
exports.assembleCatalogueRecord = assembleCatalogueRecord;
|
|
58
|
+
exports.declarationPathFor = require_storage.declarationPathFor;
|
|
54
59
|
exports.expandBucketOutputPath = require_storage.expandBucketOutputPath;
|
|
55
60
|
exports.mergeExtractedMessages = require_storage.mergeExtractedMessages;
|
|
61
|
+
exports.pruneLocaleMessages = require_storage.pruneLocaleMessages;
|
|
56
62
|
exports.readCatalogueMessages = require_storage.readCatalogueMessages;
|
|
57
|
-
exports.reconcileLocaleMessages = require_storage.reconcileLocaleMessages;
|
|
58
63
|
exports.resolveCatalogueSources = resolveCatalogueSources;
|
|
59
64
|
exports.resolveFallbackChain = resolveFallbackChain;
|
|
60
65
|
exports.writeCatalogueMessages = require_storage.writeCatalogueMessages;
|
|
@@ -9,7 +9,7 @@ declare function mergeExtractedMessages(messages: Message[]): {
|
|
|
9
9
|
id?: string | undefined;
|
|
10
10
|
context?: string | undefined;
|
|
11
11
|
}[];
|
|
12
|
-
declare function
|
|
12
|
+
declare function pruneLocaleMessages(existingMessages: Message[], sourceMessages: Message[]): {
|
|
13
13
|
message: string;
|
|
14
14
|
comments: string[];
|
|
15
15
|
references: string[];
|
|
@@ -20,6 +20,15 @@ declare function reconcileLocaleMessages(existingMessages: Message[], nextMessag
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/features/catalogue/path.d.ts
|
|
22
22
|
declare function expandBucketOutputPath(bucket: Bucket, locale: string, extension?: `.${string}`): string;
|
|
23
|
+
/**
|
|
24
|
+
* The declaration file that types a catalogue, e.g. `en.json` -> `en.d.json.ts`.
|
|
25
|
+
*
|
|
26
|
+
* TypeScript resolves `./en.json` by stripping the extension and looking for
|
|
27
|
+
* `en.d.json.ts`; the `en.json.d.ts` form is only consulted for extensions the
|
|
28
|
+
* resolver does not recognise. Non-JS extensions additionally require
|
|
29
|
+
* `allowArbitraryExtensions` in the consumer's tsconfig.
|
|
30
|
+
*/
|
|
31
|
+
declare function declarationPathFor(cataloguePath: string): string;
|
|
23
32
|
//#endregion
|
|
24
33
|
//#region src/features/catalogue/record.d.ts
|
|
25
34
|
/**
|
|
@@ -56,4 +65,4 @@ declare function readCatalogueMessages(bucket: Bucket, locale: string, path?: st
|
|
|
56
65
|
}[]>;
|
|
57
66
|
declare function writeCatalogueMessages(bucket: Bucket, locale: string, messages: Message[], path?: string): Promise<void>;
|
|
58
67
|
//#endregion
|
|
59
|
-
export { assembleCatalogueRecord, expandBucketOutputPath, mergeExtractedMessages,
|
|
68
|
+
export { assembleCatalogueRecord, declarationPathFor, expandBucketOutputPath, mergeExtractedMessages, pruneLocaleMessages, readCatalogueMessages, resolveCatalogueSources, resolveFallbackChain, writeCatalogueMessages };
|
|
@@ -9,7 +9,7 @@ declare function mergeExtractedMessages(messages: Message[]): {
|
|
|
9
9
|
id?: string | undefined;
|
|
10
10
|
context?: string | undefined;
|
|
11
11
|
}[];
|
|
12
|
-
declare function
|
|
12
|
+
declare function pruneLocaleMessages(existingMessages: Message[], sourceMessages: Message[]): {
|
|
13
13
|
message: string;
|
|
14
14
|
comments: string[];
|
|
15
15
|
references: string[];
|
|
@@ -20,6 +20,15 @@ declare function reconcileLocaleMessages(existingMessages: Message[], nextMessag
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/features/catalogue/path.d.ts
|
|
22
22
|
declare function expandBucketOutputPath(bucket: Bucket, locale: string, extension?: `.${string}`): string;
|
|
23
|
+
/**
|
|
24
|
+
* The declaration file that types a catalogue, e.g. `en.json` -> `en.d.json.ts`.
|
|
25
|
+
*
|
|
26
|
+
* TypeScript resolves `./en.json` by stripping the extension and looking for
|
|
27
|
+
* `en.d.json.ts`; the `en.json.d.ts` form is only consulted for extensions the
|
|
28
|
+
* resolver does not recognise. Non-JS extensions additionally require
|
|
29
|
+
* `allowArbitraryExtensions` in the consumer's tsconfig.
|
|
30
|
+
*/
|
|
31
|
+
declare function declarationPathFor(cataloguePath: string): string;
|
|
23
32
|
//#endregion
|
|
24
33
|
//#region src/features/catalogue/record.d.ts
|
|
25
34
|
/**
|
|
@@ -56,4 +65,4 @@ declare function readCatalogueMessages(bucket: Bucket, locale: string, path?: st
|
|
|
56
65
|
}[]>;
|
|
57
66
|
declare function writeCatalogueMessages(bucket: Bucket, locale: string, messages: Message[], path?: string): Promise<void>;
|
|
58
67
|
//#endregion
|
|
59
|
-
export { assembleCatalogueRecord, expandBucketOutputPath, mergeExtractedMessages,
|
|
68
|
+
export { assembleCatalogueRecord, declarationPathFor, expandBucketOutputPath, mergeExtractedMessages, pruneLocaleMessages, readCatalogueMessages, resolveCatalogueSources, resolveFallbackChain, writeCatalogueMessages };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as generateHash } from "../../hash-DyC8GzMa.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as mergeExtractedMessages, i as expandBucketOutputPath, n as writeCatalogueMessages, o as pruneLocaleMessages, r as declarationPathFor, t as readCatalogueMessages } from "../../storage-CqKPYfCR.mjs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
//#region src/features/catalogue/record.ts
|
|
5
5
|
/**
|
|
@@ -42,10 +42,14 @@ function assembleCatalogueRecord(bucket, contents) {
|
|
|
42
42
|
if (!content) continue;
|
|
43
43
|
for (const message of bucket.formatter.parse(content)) {
|
|
44
44
|
const key = message.id || generateHash(message.message, message.context);
|
|
45
|
-
|
|
45
|
+
if (message.translation) {
|
|
46
|
+
record[key] = message.translation;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (!record[key] && message.message) record[key] = message.message;
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
return record;
|
|
49
53
|
}
|
|
50
54
|
//#endregion
|
|
51
|
-
export { assembleCatalogueRecord, expandBucketOutputPath, mergeExtractedMessages,
|
|
55
|
+
export { assembleCatalogueRecord, declarationPathFor, expandBucketOutputPath, mergeExtractedMessages, pruneLocaleMessages, readCatalogueMessages, resolveCatalogueSources, resolveFallbackChain, writeCatalogueMessages };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as resolveConfig } from "../../loader-
|
|
1
|
+
import { t as resolveConfig } from "../../loader-BuAqYtfl.mjs";
|
|
2
2
|
export { resolveConfig };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_hash = require("../../hash-
|
|
2
|
+
const require_hash = require("../../hash-CxBlj5Dz.cjs");
|
|
3
3
|
//#region src/features/messages/identifier.ts
|
|
4
4
|
const AUTO_INCREMENT_IDENTIFIER = Symbol("auto-increment");
|
|
5
5
|
function assignSequenceIdentifiers(message, sequence = { current: 0 }) {
|
package/dist/index.cjs
CHANGED
|
@@ -1,30 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
-
get: ((k) => from[k]).bind(null, key),
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
-
value: mod,
|
|
21
|
-
enumerable: true
|
|
22
|
-
}) : target, mod));
|
|
23
|
-
//#endregion
|
|
2
|
+
const require_chunk = require("./chunk-CKQMccvm.cjs");
|
|
24
3
|
let picomatch = require("picomatch");
|
|
25
|
-
picomatch = __toESM(picomatch, 1);
|
|
4
|
+
picomatch = require_chunk.__toESM(picomatch, 1);
|
|
26
5
|
let zod = require("zod");
|
|
27
|
-
zod = __toESM(zod, 1);
|
|
6
|
+
zod = require_chunk.__toESM(zod, 1);
|
|
28
7
|
zod.object({
|
|
29
8
|
message: zod.string(),
|
|
30
9
|
translation: zod.string().optional(),
|
|
@@ -80,5 +59,4 @@ function defineConfig(config) {
|
|
|
80
59
|
return Config.parse(config);
|
|
81
60
|
}
|
|
82
61
|
//#endregion
|
|
83
|
-
exports.__toESM = __toESM;
|
|
84
62
|
exports.defineConfig = defineConfig;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import nodeModule, { createRequire } from "node:module";
|
|
2
|
+
import { dirname, extname, join, parse } from "node:path";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { tmpdir, userInfo } from "node:os";
|
|
6
|
+
//#region src/features/loader/files.ts
|
|
7
|
+
const SUPPORTED_CONFIG_FILES = [
|
|
8
|
+
"saykit.config.js",
|
|
9
|
+
"saykit.config.cjs",
|
|
10
|
+
"saykit.config.mjs",
|
|
11
|
+
"saykit.config.ts",
|
|
12
|
+
"saykit.config.mts",
|
|
13
|
+
"saykit.config.cts"
|
|
14
|
+
];
|
|
15
|
+
function getConfigFileCandidates(name) {
|
|
16
|
+
return SUPPORTED_CONFIG_FILES.map((file) => file.replace("saykit", name));
|
|
17
|
+
}
|
|
18
|
+
function findConfigFile(moduleName, projectDir) {
|
|
19
|
+
for (const fileName of getConfigFileCandidates(moduleName)) {
|
|
20
|
+
const id = join(projectDir, fileName);
|
|
21
|
+
if (existsSync(id)) return { id };
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/features/loader/module.ts
|
|
27
|
+
function findUpwards(fromPath, visit) {
|
|
28
|
+
let dir = dirname(fromPath);
|
|
29
|
+
const { root } = parse(dir);
|
|
30
|
+
while (true) {
|
|
31
|
+
const found = visit(dir);
|
|
32
|
+
if (found !== null) return found;
|
|
33
|
+
if (dir === root) return null;
|
|
34
|
+
dir = dirname(dir);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function findNearestTsConfig(fromPath) {
|
|
38
|
+
return findUpwards(fromPath, (dir) => {
|
|
39
|
+
const candidate = join(dir, "tsconfig.json");
|
|
40
|
+
return existsSync(candidate) ? candidate : null;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function digest(value) {
|
|
44
|
+
return createHash("sha1").update(value).digest("hex").slice(0, 16);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Builds are executable, so they belong beside the project's dependencies. The
|
|
48
|
+
* fallback lands in a shared temp directory instead, where a fixed path would
|
|
49
|
+
* let any other user on the machine plant code we later require — hence a
|
|
50
|
+
* per-user directory, created private in `compileToCache`.
|
|
51
|
+
*/
|
|
52
|
+
function findCacheDir(fromPath) {
|
|
53
|
+
const nodeModules = findUpwards(fromPath, (dir) => {
|
|
54
|
+
const candidate = join(dir, "node_modules");
|
|
55
|
+
return existsSync(candidate) ? candidate : null;
|
|
56
|
+
});
|
|
57
|
+
if (nodeModules) return join(nodeModules, ".cache", "saykit", "config");
|
|
58
|
+
const { uid, username, homedir } = userInfo();
|
|
59
|
+
return join(tmpdir(), `saykit-config-${digest(`${uid}\0${username}\0${homedir}`)}`);
|
|
60
|
+
}
|
|
61
|
+
function mtimeOf(path) {
|
|
62
|
+
return path ? statSync(path).mtimeMs : 0;
|
|
63
|
+
}
|
|
64
|
+
/** Removes every build of `file` in `dir` except `keep`. */
|
|
65
|
+
function pruneCache(dir, file, keep) {
|
|
66
|
+
for (const entry of readdirSync(dir)) {
|
|
67
|
+
if (!entry.startsWith(`${file}.`) || join(dir, entry) === keep) continue;
|
|
68
|
+
try {
|
|
69
|
+
unlinkSync(join(dir, entry));
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Preferred: the classic TypeScript compiler API, which honours the project's
|
|
75
|
+
* tsconfig and emits CommonJS. TypeScript 7's root export no longer ships it,
|
|
76
|
+
* and the dependency is optional, so this gives up when it isn't there.
|
|
77
|
+
*/
|
|
78
|
+
function transpileWithCompilerApi(source, tsConfigPath, require) {
|
|
79
|
+
let ts;
|
|
80
|
+
try {
|
|
81
|
+
ts = require("typescript");
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (typeof ts?.transpileModule !== "function" || typeof ts.sys?.readFile !== "function") return null;
|
|
86
|
+
const { config, error } = tsConfigPath ? ts.readConfigFile(tsConfigPath, ts.sys.readFile) : {
|
|
87
|
+
config: {},
|
|
88
|
+
error: null
|
|
89
|
+
};
|
|
90
|
+
if (error) throw error;
|
|
91
|
+
config.compilerOptions = {
|
|
92
|
+
...config.compilerOptions,
|
|
93
|
+
allowJs: true,
|
|
94
|
+
esModuleInterop: true,
|
|
95
|
+
noEmit: false,
|
|
96
|
+
module: ts.ModuleKind.CommonJS,
|
|
97
|
+
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
98
|
+
target: ts.ScriptTarget.ES2022
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
code: ts.transpileModule(source, config).outputText,
|
|
102
|
+
extension: "cjs"
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Fallback: Node erases the types itself. It ignores tsconfig and leaves the
|
|
107
|
+
* module syntax alone, so the extension has to follow the source.
|
|
108
|
+
*/
|
|
109
|
+
function transpileWithNode(source) {
|
|
110
|
+
if (typeof nodeModule.stripTypeScriptTypes !== "function") throw new Error("Loading TypeScript config files requires the TypeScript compiler API (typescript <= 6), Node 22.13+, or a runtime that loads TypeScript itself (Bun, Deno, tsx)");
|
|
111
|
+
const code = nodeModule.stripTypeScriptTypes(source, { mode: "transform" });
|
|
112
|
+
return {
|
|
113
|
+
code,
|
|
114
|
+
extension: /^\s*(?:import|export)[\s({]/m.test(code) ? "mjs" : "cjs"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Writes a plain JavaScript copy of `path` next to the project's dependencies
|
|
119
|
+
* and returns it. Copies are named `<file>.<inputs>.<extension>`, so a build
|
|
120
|
+
* can be reused until its inputs change, and earlier builds of the same file
|
|
121
|
+
* can be pruned once it does.
|
|
122
|
+
*/
|
|
123
|
+
function compileToCache(path, require) {
|
|
124
|
+
const tsConfigPath = findNearestTsConfig(path);
|
|
125
|
+
const dir = findCacheDir(path);
|
|
126
|
+
const file = digest(path);
|
|
127
|
+
const inputs = digest(`${mtimeOf(path)}\0${tsConfigPath}\0${mtimeOf(tsConfigPath)}`);
|
|
128
|
+
const cached = ["cjs", "mjs"].map((ext) => join(dir, `${file}.${inputs}.${ext}`)).find(existsSync);
|
|
129
|
+
if (cached) return cached;
|
|
130
|
+
const source = readFileSync(path, "utf8");
|
|
131
|
+
const { code, extension } = transpileWithCompilerApi(source, tsConfigPath, require) ?? transpileWithNode(source);
|
|
132
|
+
const target = join(dir, `${file}.${inputs}.${extension}`);
|
|
133
|
+
mkdirSync(dir, {
|
|
134
|
+
recursive: true,
|
|
135
|
+
mode: 448
|
|
136
|
+
});
|
|
137
|
+
writeFileSync(target, code, { mode: 384 });
|
|
138
|
+
pruneCache(dir, file, target);
|
|
139
|
+
return target;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Bun, Deno and hooks like tsx or ts-node load TypeScript better than we can,
|
|
143
|
+
* resolving tsconfig `paths` and the project's own module resolution with it.
|
|
144
|
+
*/
|
|
145
|
+
function runtimeLoadsTypeScript(require) {
|
|
146
|
+
return Boolean(process.versions.bun || globalThis.Deno || require.extensions?.[".ts"] || Reflect.get(process, Symbol.for("ts-node.register.instance")));
|
|
147
|
+
}
|
|
148
|
+
/** Requires `path` without leaving it in (or reading it from) the require cache. */
|
|
149
|
+
function requireFresh(require, path) {
|
|
150
|
+
const resolved = require.resolve(path);
|
|
151
|
+
delete require.cache[resolved];
|
|
152
|
+
const module = require(path);
|
|
153
|
+
delete require.cache[resolved];
|
|
154
|
+
return module?.default ?? module;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Loads a config file, compiling it first unless the runtime reads TypeScript
|
|
158
|
+
* on its own.
|
|
159
|
+
*/
|
|
160
|
+
function loadModule(path) {
|
|
161
|
+
const require = createRequire(path);
|
|
162
|
+
try {
|
|
163
|
+
return requireFresh(require, runtimeLoadsTypeScript(require) ? path : compileToCache(path, require));
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error("Failed to import module", { cause: error });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const js = loadModule;
|
|
169
|
+
const ts = loadModule;
|
|
170
|
+
const configLoaders = Object.freeze({
|
|
171
|
+
".js": js,
|
|
172
|
+
".mjs": js,
|
|
173
|
+
".cjs": js,
|
|
174
|
+
".ts": ts,
|
|
175
|
+
".mts": ts,
|
|
176
|
+
".cts": ts
|
|
177
|
+
});
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/features/loader/resolve.ts
|
|
180
|
+
function resolveConfig(name = "saykit") {
|
|
181
|
+
const file = findConfigFile(name, process.cwd());
|
|
182
|
+
if (!file) throw new Error(`Could not find config file for "${name}"`);
|
|
183
|
+
const ext = extname(file.id).toLowerCase();
|
|
184
|
+
const load = ext in configLoaders ? configLoaders[ext] : null;
|
|
185
|
+
if (!load) throw new Error(`Unsupported config file type "${ext}" for "${name}"`);
|
|
186
|
+
const config = load(file.id);
|
|
187
|
+
if (!config || typeof config !== "object") throw new Error(`Invalid config file for "${name}"`);
|
|
188
|
+
return config;
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
export { resolveConfig as t };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CKQMccvm.cjs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
let node_fs = require("node:fs");
|
|
4
|
+
let node_crypto = require("node:crypto");
|
|
5
|
+
let node_module = require("node:module");
|
|
6
|
+
node_module = require_chunk.__toESM(node_module, 1);
|
|
7
|
+
let node_os = require("node:os");
|
|
8
|
+
//#region src/features/loader/files.ts
|
|
9
|
+
const SUPPORTED_CONFIG_FILES = [
|
|
10
|
+
"saykit.config.js",
|
|
11
|
+
"saykit.config.cjs",
|
|
12
|
+
"saykit.config.mjs",
|
|
13
|
+
"saykit.config.ts",
|
|
14
|
+
"saykit.config.mts",
|
|
15
|
+
"saykit.config.cts"
|
|
16
|
+
];
|
|
17
|
+
function getConfigFileCandidates(name) {
|
|
18
|
+
return SUPPORTED_CONFIG_FILES.map((file) => file.replace("saykit", name));
|
|
19
|
+
}
|
|
20
|
+
function findConfigFile(moduleName, projectDir) {
|
|
21
|
+
for (const fileName of getConfigFileCandidates(moduleName)) {
|
|
22
|
+
const id = (0, node_path.join)(projectDir, fileName);
|
|
23
|
+
if ((0, node_fs.existsSync)(id)) return { id };
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/features/loader/module.ts
|
|
29
|
+
function findUpwards(fromPath, visit) {
|
|
30
|
+
let dir = (0, node_path.dirname)(fromPath);
|
|
31
|
+
const { root } = (0, node_path.parse)(dir);
|
|
32
|
+
while (true) {
|
|
33
|
+
const found = visit(dir);
|
|
34
|
+
if (found !== null) return found;
|
|
35
|
+
if (dir === root) return null;
|
|
36
|
+
dir = (0, node_path.dirname)(dir);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function findNearestTsConfig(fromPath) {
|
|
40
|
+
return findUpwards(fromPath, (dir) => {
|
|
41
|
+
const candidate = (0, node_path.join)(dir, "tsconfig.json");
|
|
42
|
+
return (0, node_fs.existsSync)(candidate) ? candidate : null;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function digest(value) {
|
|
46
|
+
return (0, node_crypto.createHash)("sha1").update(value).digest("hex").slice(0, 16);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds are executable, so they belong beside the project's dependencies. The
|
|
50
|
+
* fallback lands in a shared temp directory instead, where a fixed path would
|
|
51
|
+
* let any other user on the machine plant code we later require — hence a
|
|
52
|
+
* per-user directory, created private in `compileToCache`.
|
|
53
|
+
*/
|
|
54
|
+
function findCacheDir(fromPath) {
|
|
55
|
+
const nodeModules = findUpwards(fromPath, (dir) => {
|
|
56
|
+
const candidate = (0, node_path.join)(dir, "node_modules");
|
|
57
|
+
return (0, node_fs.existsSync)(candidate) ? candidate : null;
|
|
58
|
+
});
|
|
59
|
+
if (nodeModules) return (0, node_path.join)(nodeModules, ".cache", "saykit", "config");
|
|
60
|
+
const { uid, username, homedir } = (0, node_os.userInfo)();
|
|
61
|
+
return (0, node_path.join)((0, node_os.tmpdir)(), `saykit-config-${digest(`${uid}\0${username}\0${homedir}`)}`);
|
|
62
|
+
}
|
|
63
|
+
function mtimeOf(path) {
|
|
64
|
+
return path ? (0, node_fs.statSync)(path).mtimeMs : 0;
|
|
65
|
+
}
|
|
66
|
+
/** Removes every build of `file` in `dir` except `keep`. */
|
|
67
|
+
function pruneCache(dir, file, keep) {
|
|
68
|
+
for (const entry of (0, node_fs.readdirSync)(dir)) {
|
|
69
|
+
if (!entry.startsWith(`${file}.`) || (0, node_path.join)(dir, entry) === keep) continue;
|
|
70
|
+
try {
|
|
71
|
+
(0, node_fs.unlinkSync)((0, node_path.join)(dir, entry));
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Preferred: the classic TypeScript compiler API, which honours the project's
|
|
77
|
+
* tsconfig and emits CommonJS. TypeScript 7's root export no longer ships it,
|
|
78
|
+
* and the dependency is optional, so this gives up when it isn't there.
|
|
79
|
+
*/
|
|
80
|
+
function transpileWithCompilerApi(source, tsConfigPath, require) {
|
|
81
|
+
let ts;
|
|
82
|
+
try {
|
|
83
|
+
ts = require("typescript");
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (typeof ts?.transpileModule !== "function" || typeof ts.sys?.readFile !== "function") return null;
|
|
88
|
+
const { config, error } = tsConfigPath ? ts.readConfigFile(tsConfigPath, ts.sys.readFile) : {
|
|
89
|
+
config: {},
|
|
90
|
+
error: null
|
|
91
|
+
};
|
|
92
|
+
if (error) throw error;
|
|
93
|
+
config.compilerOptions = {
|
|
94
|
+
...config.compilerOptions,
|
|
95
|
+
allowJs: true,
|
|
96
|
+
esModuleInterop: true,
|
|
97
|
+
noEmit: false,
|
|
98
|
+
module: ts.ModuleKind.CommonJS,
|
|
99
|
+
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
100
|
+
target: ts.ScriptTarget.ES2022
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
code: ts.transpileModule(source, config).outputText,
|
|
104
|
+
extension: "cjs"
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Fallback: Node erases the types itself. It ignores tsconfig and leaves the
|
|
109
|
+
* module syntax alone, so the extension has to follow the source.
|
|
110
|
+
*/
|
|
111
|
+
function transpileWithNode(source) {
|
|
112
|
+
if (typeof node_module.default.stripTypeScriptTypes !== "function") throw new Error("Loading TypeScript config files requires the TypeScript compiler API (typescript <= 6), Node 22.13+, or a runtime that loads TypeScript itself (Bun, Deno, tsx)");
|
|
113
|
+
const code = node_module.default.stripTypeScriptTypes(source, { mode: "transform" });
|
|
114
|
+
return {
|
|
115
|
+
code,
|
|
116
|
+
extension: /^\s*(?:import|export)[\s({]/m.test(code) ? "mjs" : "cjs"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Writes a plain JavaScript copy of `path` next to the project's dependencies
|
|
121
|
+
* and returns it. Copies are named `<file>.<inputs>.<extension>`, so a build
|
|
122
|
+
* can be reused until its inputs change, and earlier builds of the same file
|
|
123
|
+
* can be pruned once it does.
|
|
124
|
+
*/
|
|
125
|
+
function compileToCache(path, require) {
|
|
126
|
+
const tsConfigPath = findNearestTsConfig(path);
|
|
127
|
+
const dir = findCacheDir(path);
|
|
128
|
+
const file = digest(path);
|
|
129
|
+
const inputs = digest(`${mtimeOf(path)}\0${tsConfigPath}\0${mtimeOf(tsConfigPath)}`);
|
|
130
|
+
const cached = ["cjs", "mjs"].map((ext) => (0, node_path.join)(dir, `${file}.${inputs}.${ext}`)).find(node_fs.existsSync);
|
|
131
|
+
if (cached) return cached;
|
|
132
|
+
const source = (0, node_fs.readFileSync)(path, "utf8");
|
|
133
|
+
const { code, extension } = transpileWithCompilerApi(source, tsConfigPath, require) ?? transpileWithNode(source);
|
|
134
|
+
const target = (0, node_path.join)(dir, `${file}.${inputs}.${extension}`);
|
|
135
|
+
(0, node_fs.mkdirSync)(dir, {
|
|
136
|
+
recursive: true,
|
|
137
|
+
mode: 448
|
|
138
|
+
});
|
|
139
|
+
(0, node_fs.writeFileSync)(target, code, { mode: 384 });
|
|
140
|
+
pruneCache(dir, file, target);
|
|
141
|
+
return target;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Bun, Deno and hooks like tsx or ts-node load TypeScript better than we can,
|
|
145
|
+
* resolving tsconfig `paths` and the project's own module resolution with it.
|
|
146
|
+
*/
|
|
147
|
+
function runtimeLoadsTypeScript(require) {
|
|
148
|
+
return Boolean(process.versions.bun || globalThis.Deno || require.extensions?.[".ts"] || Reflect.get(process, Symbol.for("ts-node.register.instance")));
|
|
149
|
+
}
|
|
150
|
+
/** Requires `path` without leaving it in (or reading it from) the require cache. */
|
|
151
|
+
function requireFresh(require, path) {
|
|
152
|
+
const resolved = require.resolve(path);
|
|
153
|
+
delete require.cache[resolved];
|
|
154
|
+
const module = require(path);
|
|
155
|
+
delete require.cache[resolved];
|
|
156
|
+
return module?.default ?? module;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Loads a config file, compiling it first unless the runtime reads TypeScript
|
|
160
|
+
* on its own.
|
|
161
|
+
*/
|
|
162
|
+
function loadModule(path) {
|
|
163
|
+
const require = (0, node_module.createRequire)(path);
|
|
164
|
+
try {
|
|
165
|
+
return requireFresh(require, runtimeLoadsTypeScript(require) ? path : compileToCache(path, require));
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw new Error("Failed to import module", { cause: error });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const js = loadModule;
|
|
171
|
+
const ts = loadModule;
|
|
172
|
+
const configLoaders = Object.freeze({
|
|
173
|
+
".js": js,
|
|
174
|
+
".mjs": js,
|
|
175
|
+
".cjs": js,
|
|
176
|
+
".ts": ts,
|
|
177
|
+
".mts": ts,
|
|
178
|
+
".cts": ts
|
|
179
|
+
});
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/features/loader/resolve.ts
|
|
182
|
+
function resolveConfig(name = "saykit") {
|
|
183
|
+
const file = findConfigFile(name, process.cwd());
|
|
184
|
+
if (!file) throw new Error(`Could not find config file for "${name}"`);
|
|
185
|
+
const ext = (0, node_path.extname)(file.id).toLowerCase();
|
|
186
|
+
const load = ext in configLoaders ? configLoaders[ext] : null;
|
|
187
|
+
if (!load) throw new Error(`Unsupported config file type "${ext}" for "${name}"`);
|
|
188
|
+
const config = load(file.id);
|
|
189
|
+
if (!config || typeof config !== "object") throw new Error(`Invalid config file for "${name}"`);
|
|
190
|
+
return config;
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
Object.defineProperty(exports, "resolveConfig", {
|
|
194
|
+
enumerable: true,
|
|
195
|
+
get: function() {
|
|
196
|
+
return resolveConfig;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as generateHash } from "./hash-DyC8GzMa.mjs";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { dirname, parse, resolve } from "node:path";
|
|
4
4
|
//#region src/features/catalogue/merge.ts
|
|
5
5
|
function mergeUnique(...items) {
|
|
6
6
|
return Array.from(new Set(items.flat()));
|
|
@@ -21,33 +21,33 @@ function mergeExtractedMessages(messages) {
|
|
|
21
21
|
}, /* @__PURE__ */ new Map());
|
|
22
22
|
return Array.from(mergedMessages.values());
|
|
23
23
|
}
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
return map;
|
|
28
|
-
}, /* @__PURE__ */ new Map());
|
|
29
|
-
const reconciledMessages = nextMessages.reduce((map, message) => {
|
|
30
|
-
const key = getMessageKey(message);
|
|
31
|
-
const existingMessage = existingMessagesByKey.get(key);
|
|
32
|
-
map.set(key, {
|
|
33
|
-
...message,
|
|
34
|
-
translation: existingMessage?.translation
|
|
35
|
-
});
|
|
36
|
-
return map;
|
|
37
|
-
}, /* @__PURE__ */ new Map());
|
|
38
|
-
return Array.from(reconciledMessages.values());
|
|
24
|
+
function pruneLocaleMessages(existingMessages, sourceMessages) {
|
|
25
|
+
const sourceKeys = new Set(sourceMessages.map(getMessageKey));
|
|
26
|
+
return existingMessages.filter((message) => sourceKeys.has(getMessageKey(message)) && Boolean(message.translation));
|
|
39
27
|
}
|
|
40
28
|
//#endregion
|
|
41
29
|
//#region src/features/catalogue/path.ts
|
|
42
30
|
function expandBucketOutputPath(bucket, locale, extension = bucket.formatter.extension) {
|
|
43
31
|
return resolve(bucket.output.replaceAll("{locale}", locale).replaceAll("{extension}", extension.slice(1)));
|
|
44
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The declaration file that types a catalogue, e.g. `en.json` -> `en.d.json.ts`.
|
|
35
|
+
*
|
|
36
|
+
* TypeScript resolves `./en.json` by stripping the extension and looking for
|
|
37
|
+
* `en.d.json.ts`; the `en.json.d.ts` form is only consulted for extensions the
|
|
38
|
+
* resolver does not recognise. Non-JS extensions additionally require
|
|
39
|
+
* `allowArbitraryExtensions` in the consumer's tsconfig.
|
|
40
|
+
*/
|
|
41
|
+
function declarationPathFor(cataloguePath) {
|
|
42
|
+
const { dir, name, ext } = parse(cataloguePath);
|
|
43
|
+
return resolve(dir, `${name}.d${ext}.ts`);
|
|
44
|
+
}
|
|
45
45
|
//#endregion
|
|
46
46
|
//#region src/features/catalogue/storage.ts
|
|
47
47
|
const DECLARATION_CONTENT = `
|
|
48
48
|
declare const messages: Record<string, string>;
|
|
49
49
|
export default messages;
|
|
50
|
-
`.
|
|
50
|
+
`.trimStart();
|
|
51
51
|
async function readCatalogueMessages(bucket, locale, path = expandBucketOutputPath(bucket, locale)) {
|
|
52
52
|
const content = await readFile(path, "utf8").catch(() => "");
|
|
53
53
|
if (!content) return [];
|
|
@@ -59,9 +59,9 @@ async function writeCatalogueMessages(bucket, locale, messages, path = expandBuc
|
|
|
59
59
|
locale,
|
|
60
60
|
existingContent
|
|
61
61
|
});
|
|
62
|
-
const declarationPath =
|
|
62
|
+
const declarationPath = declarationPathFor(path);
|
|
63
63
|
await mkdir(dirname(path), { recursive: true });
|
|
64
64
|
await Promise.all([writeFile(path, catalogueContent), writeFile(declarationPath, DECLARATION_CONTENT)]);
|
|
65
65
|
}
|
|
66
66
|
//#endregion
|
|
67
|
-
export {
|
|
67
|
+
export { mergeExtractedMessages as a, expandBucketOutputPath as i, writeCatalogueMessages as n, pruneLocaleMessages as o, declarationPathFor as r, readCatalogueMessages as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require("./
|
|
2
|
-
const require_hash = require("./hash-
|
|
1
|
+
require("./chunk-CKQMccvm.cjs");
|
|
2
|
+
const require_hash = require("./hash-CxBlj5Dz.cjs");
|
|
3
3
|
let node_fs_promises = require("node:fs/promises");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
//#region src/features/catalogue/merge.ts
|
|
@@ -22,33 +22,33 @@ function mergeExtractedMessages(messages) {
|
|
|
22
22
|
}, /* @__PURE__ */ new Map());
|
|
23
23
|
return Array.from(mergedMessages.values());
|
|
24
24
|
}
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
return map;
|
|
29
|
-
}, /* @__PURE__ */ new Map());
|
|
30
|
-
const reconciledMessages = nextMessages.reduce((map, message) => {
|
|
31
|
-
const key = getMessageKey(message);
|
|
32
|
-
const existingMessage = existingMessagesByKey.get(key);
|
|
33
|
-
map.set(key, {
|
|
34
|
-
...message,
|
|
35
|
-
translation: existingMessage?.translation
|
|
36
|
-
});
|
|
37
|
-
return map;
|
|
38
|
-
}, /* @__PURE__ */ new Map());
|
|
39
|
-
return Array.from(reconciledMessages.values());
|
|
25
|
+
function pruneLocaleMessages(existingMessages, sourceMessages) {
|
|
26
|
+
const sourceKeys = new Set(sourceMessages.map(getMessageKey));
|
|
27
|
+
return existingMessages.filter((message) => sourceKeys.has(getMessageKey(message)) && Boolean(message.translation));
|
|
40
28
|
}
|
|
41
29
|
//#endregion
|
|
42
30
|
//#region src/features/catalogue/path.ts
|
|
43
31
|
function expandBucketOutputPath(bucket, locale, extension = bucket.formatter.extension) {
|
|
44
32
|
return (0, node_path.resolve)(bucket.output.replaceAll("{locale}", locale).replaceAll("{extension}", extension.slice(1)));
|
|
45
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The declaration file that types a catalogue, e.g. `en.json` -> `en.d.json.ts`.
|
|
36
|
+
*
|
|
37
|
+
* TypeScript resolves `./en.json` by stripping the extension and looking for
|
|
38
|
+
* `en.d.json.ts`; the `en.json.d.ts` form is only consulted for extensions the
|
|
39
|
+
* resolver does not recognise. Non-JS extensions additionally require
|
|
40
|
+
* `allowArbitraryExtensions` in the consumer's tsconfig.
|
|
41
|
+
*/
|
|
42
|
+
function declarationPathFor(cataloguePath) {
|
|
43
|
+
const { dir, name, ext } = (0, node_path.parse)(cataloguePath);
|
|
44
|
+
return (0, node_path.resolve)(dir, `${name}.d${ext}.ts`);
|
|
45
|
+
}
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/features/catalogue/storage.ts
|
|
48
48
|
const DECLARATION_CONTENT = `
|
|
49
49
|
declare const messages: Record<string, string>;
|
|
50
50
|
export default messages;
|
|
51
|
-
`.
|
|
51
|
+
`.trimStart();
|
|
52
52
|
async function readCatalogueMessages(bucket, locale, path = expandBucketOutputPath(bucket, locale)) {
|
|
53
53
|
const content = await (0, node_fs_promises.readFile)(path, "utf8").catch(() => "");
|
|
54
54
|
if (!content) return [];
|
|
@@ -60,11 +60,17 @@ async function writeCatalogueMessages(bucket, locale, messages, path = expandBuc
|
|
|
60
60
|
locale,
|
|
61
61
|
existingContent
|
|
62
62
|
});
|
|
63
|
-
const declarationPath =
|
|
63
|
+
const declarationPath = declarationPathFor(path);
|
|
64
64
|
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
65
65
|
await Promise.all([(0, node_fs_promises.writeFile)(path, catalogueContent), (0, node_fs_promises.writeFile)(declarationPath, DECLARATION_CONTENT)]);
|
|
66
66
|
}
|
|
67
67
|
//#endregion
|
|
68
|
+
Object.defineProperty(exports, "declarationPathFor", {
|
|
69
|
+
enumerable: true,
|
|
70
|
+
get: function() {
|
|
71
|
+
return declarationPathFor;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
68
74
|
Object.defineProperty(exports, "expandBucketOutputPath", {
|
|
69
75
|
enumerable: true,
|
|
70
76
|
get: function() {
|
|
@@ -77,16 +83,16 @@ Object.defineProperty(exports, "mergeExtractedMessages", {
|
|
|
77
83
|
return mergeExtractedMessages;
|
|
78
84
|
}
|
|
79
85
|
});
|
|
80
|
-
Object.defineProperty(exports, "
|
|
86
|
+
Object.defineProperty(exports, "pruneLocaleMessages", {
|
|
81
87
|
enumerable: true,
|
|
82
88
|
get: function() {
|
|
83
|
-
return
|
|
89
|
+
return pruneLocaleMessages;
|
|
84
90
|
}
|
|
85
91
|
});
|
|
86
|
-
Object.defineProperty(exports, "
|
|
92
|
+
Object.defineProperty(exports, "readCatalogueMessages", {
|
|
87
93
|
enumerable: true,
|
|
88
94
|
get: function() {
|
|
89
|
-
return
|
|
95
|
+
return readCatalogueMessages;
|
|
90
96
|
}
|
|
91
97
|
});
|
|
92
98
|
Object.defineProperty(exports, "writeCatalogueMessages", {
|
package/package.json
CHANGED
package/dist/loader-B25O-vF6.cjs
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
require("./index.cjs");
|
|
2
|
-
let node_path = require("node:path");
|
|
3
|
-
let node_fs = require("node:fs");
|
|
4
|
-
let node_crypto = require("node:crypto");
|
|
5
|
-
let node_module = require("node:module");
|
|
6
|
-
let node_os = require("node:os");
|
|
7
|
-
//#region src/features/loader/files.ts
|
|
8
|
-
const SUPPORTED_CONFIG_FILES = [
|
|
9
|
-
"saykit.config.js",
|
|
10
|
-
"saykit.config.cjs",
|
|
11
|
-
"saykit.config.mjs",
|
|
12
|
-
"saykit.config.ts",
|
|
13
|
-
"saykit.config.mts",
|
|
14
|
-
"saykit.config.cts"
|
|
15
|
-
];
|
|
16
|
-
function getConfigFileCandidates(name) {
|
|
17
|
-
return SUPPORTED_CONFIG_FILES.map((file) => file.replace("saykit", name));
|
|
18
|
-
}
|
|
19
|
-
function findConfigFile(moduleName, projectDir) {
|
|
20
|
-
for (const fileName of getConfigFileCandidates(moduleName)) {
|
|
21
|
-
const id = (0, node_path.join)(projectDir, fileName);
|
|
22
|
-
if ((0, node_fs.existsSync)(id)) return { id };
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/features/loader/module.ts
|
|
28
|
-
function findNearestTsConfig(fromPath) {
|
|
29
|
-
let dir = (0, node_path.dirname)(fromPath);
|
|
30
|
-
const { root } = (0, node_path.parse)(dir);
|
|
31
|
-
while (true) {
|
|
32
|
-
const candidate = (0, node_path.join)(dir, "tsconfig.json");
|
|
33
|
-
if ((0, node_fs.existsSync)(candidate)) return candidate;
|
|
34
|
-
if (dir === root) return null;
|
|
35
|
-
dir = (0, node_path.dirname)(dir);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function findCacheDir(fromPath) {
|
|
39
|
-
let dir = (0, node_path.dirname)(fromPath);
|
|
40
|
-
const { root } = (0, node_path.parse)(dir);
|
|
41
|
-
while (true) {
|
|
42
|
-
const nm = (0, node_path.join)(dir, "node_modules");
|
|
43
|
-
if ((0, node_fs.existsSync)(nm)) return (0, node_path.join)(nm, ".cache", "saykit", "config");
|
|
44
|
-
if (dir === root) return (0, node_path.join)((0, node_os.tmpdir)(), "saykit", "config");
|
|
45
|
-
dir = (0, node_path.dirname)(dir);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function transpile(path, tsConfigPath, require) {
|
|
49
|
-
const ts = require("typescript");
|
|
50
|
-
const { config: tsConfig, error } = tsConfigPath ? ts.readConfigFile(tsConfigPath, ts.sys.readFile) : {
|
|
51
|
-
config: {},
|
|
52
|
-
error: null
|
|
53
|
-
};
|
|
54
|
-
if (error) throw error;
|
|
55
|
-
tsConfig.compilerOptions = {
|
|
56
|
-
...tsConfig.compilerOptions,
|
|
57
|
-
allowJs: true,
|
|
58
|
-
esModuleInterop: true,
|
|
59
|
-
module: ts.ModuleKind.CommonJS,
|
|
60
|
-
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
61
|
-
target: ts.ScriptTarget.ES2022,
|
|
62
|
-
noEmit: false
|
|
63
|
-
};
|
|
64
|
-
return ts.transpileModule((0, node_fs.readFileSync)(path, "utf8"), tsConfig).outputText;
|
|
65
|
-
}
|
|
66
|
-
function loadWithCache(path) {
|
|
67
|
-
const require = (0, node_module.createRequire)(path);
|
|
68
|
-
const mtimeMs = (0, node_fs.statSync)(path).mtimeMs;
|
|
69
|
-
const tsConfigPath = findNearestTsConfig(path);
|
|
70
|
-
const tsConfigMtimeMs = tsConfigPath ? (0, node_fs.statSync)(tsConfigPath).mtimeMs : 0;
|
|
71
|
-
const hash = (0, node_crypto.createHash)("sha1").update(`${path}\0${tsConfigPath ?? ""}\0${tsConfigMtimeMs}`).digest("hex").slice(0, 16);
|
|
72
|
-
const cacheDir = findCacheDir(path);
|
|
73
|
-
const cachePath = (0, node_path.join)(cacheDir, `${hash}.${mtimeMs}.cjs`);
|
|
74
|
-
try {
|
|
75
|
-
if (!(0, node_fs.existsSync)(cachePath)) {
|
|
76
|
-
(0, node_fs.mkdirSync)(cacheDir, { recursive: true });
|
|
77
|
-
(0, node_fs.writeFileSync)(cachePath, transpile(path, tsConfigPath, require));
|
|
78
|
-
if ((0, node_fs.existsSync)(cacheDir)) {
|
|
79
|
-
for (const entry of (0, node_fs.readdirSync)(cacheDir)) if (entry.startsWith(`${hash}.`) && entry !== `${hash}.${mtimeMs}.cjs`) try {
|
|
80
|
-
(0, node_fs.unlinkSync)((0, node_path.join)(cacheDir, entry));
|
|
81
|
-
} catch {}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const resolved = require.resolve(cachePath);
|
|
85
|
-
delete require.cache[resolved];
|
|
86
|
-
const module = require(cachePath);
|
|
87
|
-
delete require.cache[resolved];
|
|
88
|
-
return module?.default ?? module;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
throw new Error("Failed to import module", { cause: error });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const js = (path) => loadWithCache(path);
|
|
94
|
-
const ts = (path) => loadWithCache(path);
|
|
95
|
-
const configLoaders = Object.freeze({
|
|
96
|
-
".js": js,
|
|
97
|
-
".mjs": js,
|
|
98
|
-
".cjs": js,
|
|
99
|
-
".ts": ts,
|
|
100
|
-
".mts": ts,
|
|
101
|
-
".cts": ts
|
|
102
|
-
});
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/features/loader/resolve.ts
|
|
105
|
-
function resolveConfig(name = "saykit") {
|
|
106
|
-
const file = findConfigFile(name, process.cwd());
|
|
107
|
-
if (!file) throw new Error(`Could not find config file for "${name}"`);
|
|
108
|
-
const ext = (0, node_path.extname)(file.id).toLowerCase();
|
|
109
|
-
const load = ext in configLoaders ? configLoaders[ext] : null;
|
|
110
|
-
if (!load) throw new Error(`Unsupported config file type "${ext}" for "${name}"`);
|
|
111
|
-
const config = load(file.id);
|
|
112
|
-
if (!config || typeof config !== "object") throw new Error(`Invalid config file for "${name}"`);
|
|
113
|
-
return config;
|
|
114
|
-
}
|
|
115
|
-
//#endregion
|
|
116
|
-
Object.defineProperty(exports, "resolveConfig", {
|
|
117
|
-
enumerable: true,
|
|
118
|
-
get: function() {
|
|
119
|
-
return resolveConfig;
|
|
120
|
-
}
|
|
121
|
-
});
|
package/dist/loader-C2eolE_1.mjs
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { dirname, extname, join, parse } from "node:path";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
//#region src/features/loader/files.ts
|
|
7
|
-
const SUPPORTED_CONFIG_FILES = [
|
|
8
|
-
"saykit.config.js",
|
|
9
|
-
"saykit.config.cjs",
|
|
10
|
-
"saykit.config.mjs",
|
|
11
|
-
"saykit.config.ts",
|
|
12
|
-
"saykit.config.mts",
|
|
13
|
-
"saykit.config.cts"
|
|
14
|
-
];
|
|
15
|
-
function getConfigFileCandidates(name) {
|
|
16
|
-
return SUPPORTED_CONFIG_FILES.map((file) => file.replace("saykit", name));
|
|
17
|
-
}
|
|
18
|
-
function findConfigFile(moduleName, projectDir) {
|
|
19
|
-
for (const fileName of getConfigFileCandidates(moduleName)) {
|
|
20
|
-
const id = join(projectDir, fileName);
|
|
21
|
-
if (existsSync(id)) return { id };
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/features/loader/module.ts
|
|
27
|
-
function findNearestTsConfig(fromPath) {
|
|
28
|
-
let dir = dirname(fromPath);
|
|
29
|
-
const { root } = parse(dir);
|
|
30
|
-
while (true) {
|
|
31
|
-
const candidate = join(dir, "tsconfig.json");
|
|
32
|
-
if (existsSync(candidate)) return candidate;
|
|
33
|
-
if (dir === root) return null;
|
|
34
|
-
dir = dirname(dir);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function findCacheDir(fromPath) {
|
|
38
|
-
let dir = dirname(fromPath);
|
|
39
|
-
const { root } = parse(dir);
|
|
40
|
-
while (true) {
|
|
41
|
-
const nm = join(dir, "node_modules");
|
|
42
|
-
if (existsSync(nm)) return join(nm, ".cache", "saykit", "config");
|
|
43
|
-
if (dir === root) return join(tmpdir(), "saykit", "config");
|
|
44
|
-
dir = dirname(dir);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function transpile(path, tsConfigPath, require) {
|
|
48
|
-
const ts = require("typescript");
|
|
49
|
-
const { config: tsConfig, error } = tsConfigPath ? ts.readConfigFile(tsConfigPath, ts.sys.readFile) : {
|
|
50
|
-
config: {},
|
|
51
|
-
error: null
|
|
52
|
-
};
|
|
53
|
-
if (error) throw error;
|
|
54
|
-
tsConfig.compilerOptions = {
|
|
55
|
-
...tsConfig.compilerOptions,
|
|
56
|
-
allowJs: true,
|
|
57
|
-
esModuleInterop: true,
|
|
58
|
-
module: ts.ModuleKind.CommonJS,
|
|
59
|
-
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
60
|
-
target: ts.ScriptTarget.ES2022,
|
|
61
|
-
noEmit: false
|
|
62
|
-
};
|
|
63
|
-
return ts.transpileModule(readFileSync(path, "utf8"), tsConfig).outputText;
|
|
64
|
-
}
|
|
65
|
-
function loadWithCache(path) {
|
|
66
|
-
const require = createRequire(path);
|
|
67
|
-
const mtimeMs = statSync(path).mtimeMs;
|
|
68
|
-
const tsConfigPath = findNearestTsConfig(path);
|
|
69
|
-
const tsConfigMtimeMs = tsConfigPath ? statSync(tsConfigPath).mtimeMs : 0;
|
|
70
|
-
const hash = createHash("sha1").update(`${path}\0${tsConfigPath ?? ""}\0${tsConfigMtimeMs}`).digest("hex").slice(0, 16);
|
|
71
|
-
const cacheDir = findCacheDir(path);
|
|
72
|
-
const cachePath = join(cacheDir, `${hash}.${mtimeMs}.cjs`);
|
|
73
|
-
try {
|
|
74
|
-
if (!existsSync(cachePath)) {
|
|
75
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
76
|
-
writeFileSync(cachePath, transpile(path, tsConfigPath, require));
|
|
77
|
-
if (existsSync(cacheDir)) {
|
|
78
|
-
for (const entry of readdirSync(cacheDir)) if (entry.startsWith(`${hash}.`) && entry !== `${hash}.${mtimeMs}.cjs`) try {
|
|
79
|
-
unlinkSync(join(cacheDir, entry));
|
|
80
|
-
} catch {}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const resolved = require.resolve(cachePath);
|
|
84
|
-
delete require.cache[resolved];
|
|
85
|
-
const module = require(cachePath);
|
|
86
|
-
delete require.cache[resolved];
|
|
87
|
-
return module?.default ?? module;
|
|
88
|
-
} catch (error) {
|
|
89
|
-
throw new Error("Failed to import module", { cause: error });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const js = (path) => loadWithCache(path);
|
|
93
|
-
const ts = (path) => loadWithCache(path);
|
|
94
|
-
const configLoaders = Object.freeze({
|
|
95
|
-
".js": js,
|
|
96
|
-
".mjs": js,
|
|
97
|
-
".cjs": js,
|
|
98
|
-
".ts": ts,
|
|
99
|
-
".mts": ts,
|
|
100
|
-
".cts": ts
|
|
101
|
-
});
|
|
102
|
-
//#endregion
|
|
103
|
-
//#region src/features/loader/resolve.ts
|
|
104
|
-
function resolveConfig(name = "saykit") {
|
|
105
|
-
const file = findConfigFile(name, process.cwd());
|
|
106
|
-
if (!file) throw new Error(`Could not find config file for "${name}"`);
|
|
107
|
-
const ext = extname(file.id).toLowerCase();
|
|
108
|
-
const load = ext in configLoaders ? configLoaders[ext] : null;
|
|
109
|
-
if (!load) throw new Error(`Unsupported config file type "${ext}" for "${name}"`);
|
|
110
|
-
const config = load(file.id);
|
|
111
|
-
if (!config || typeof config !== "object") throw new Error(`Invalid config file for "${name}"`);
|
|
112
|
-
return config;
|
|
113
|
-
}
|
|
114
|
-
//#endregion
|
|
115
|
-
export { resolveConfig as t };
|