@heroiclands/package-build 0.4.0 → 0.6.0
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/CHANGELOG.md +115 -0
- package/README.md +238 -2
- package/bin/package-build.mjs +482 -49
- package/bin/report.mjs +13 -4
- package/config.mjs +380 -1
- package/container.mjs +699 -0
- package/coverage.mjs +626 -0
- package/e2e.mjs +876 -0
- package/index.mjs +11 -2
- package/lang.mjs +2 -2
- package/package.json +34 -17
- package/templates.mjs +224 -0
- package/types/config.d.mts +129 -0
- package/types/container.d.mts +359 -0
- package/types/coverage.d.mts +182 -0
- package/types/e2e.d.mts +351 -0
- package/types/index.d.mts +4 -1
- package/types/templates.d.mts +74 -0
- package/text.mjs +0 -74
- package/types/text.d.mts +0 -51
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The environment variable naming a stage's Foundry data root.
|
|
3
|
+
*
|
|
4
|
+
* Derived rather than tabulated, so a repository that adds a stage of its own
|
|
5
|
+
* gets the variable without this package learning its name.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} stage - The stage name.
|
|
8
|
+
* @returns {string} The variable to read.
|
|
9
|
+
*/
|
|
10
|
+
export function dataEnvVar(stage: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* The container name for a package's stage.
|
|
13
|
+
*
|
|
14
|
+
* Named after the package so two HeroicLands packages can run their own
|
|
15
|
+
* containers side by side, and stable so Foundry's signed licence — which is
|
|
16
|
+
* bound to the container hostname — survives a recreate.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} packageId - The Foundry package id.
|
|
19
|
+
* @param {string} stage - The stage name.
|
|
20
|
+
* @returns {string} The container name.
|
|
21
|
+
*/
|
|
22
|
+
export function containerName(packageId: string, stage: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* A stage declared in `packageBuild.container.stages`.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {object} ContainerStage
|
|
27
|
+
* @property {number|null} port Host port to publish.
|
|
28
|
+
* @property {string|null} world World to auto-launch; `""` forces none.
|
|
29
|
+
* @property {string|null} version Exact Foundry build to pin.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* The host port a stage publishes.
|
|
33
|
+
*
|
|
34
|
+
* `FOUNDRYVTT_<STAGE>_PORT` first, then the stage's declared port, then the
|
|
35
|
+
* conventional default.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} stage - The stage name.
|
|
38
|
+
* @param {object} [opts]
|
|
39
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
|
|
40
|
+
* @param {Record<string, ContainerStage>} [opts.stages] - Declared stages.
|
|
41
|
+
* @returns {number} The host port.
|
|
42
|
+
* @throws {Error} When the stage has no port from any source.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveStagePort(stage: string, { env, stages }?: {
|
|
45
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
46
|
+
stages?: Record<string, ContainerStage> | undefined;
|
|
47
|
+
}): number;
|
|
48
|
+
/**
|
|
49
|
+
* The exact Foundry build a stage is pinned to, or `null` to float.
|
|
50
|
+
*
|
|
51
|
+
* `FOUNDRYVTT_<STAGE>_VERSION` wins — that is how a sweep runs against a build
|
|
52
|
+
* without touching committed configuration. Then the stage's own declared
|
|
53
|
+
* version. Then, **for the end-to-end stage only**, the package's
|
|
54
|
+
* `compatibility.minimum`.
|
|
55
|
+
*
|
|
56
|
+
* That last rule is the point. `compatibility.minimum` is a promise the
|
|
57
|
+
* manifest makes to every user, and a promise is only defended if something
|
|
58
|
+
* exercises it: a regression that breaks the floor while working on a newer
|
|
59
|
+
* build passes a suite run above the floor in silence. Deriving the pin from
|
|
60
|
+
* the claim means the evidence and the claim are the same number, and neither
|
|
61
|
+
* can drift from the other.
|
|
62
|
+
*
|
|
63
|
+
* A floor that names no build (`"14"`) cannot pin one, so the run floats on the
|
|
64
|
+
* major tag — visibly, rather than by pretending to a precision it lacks.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} stage - The stage name.
|
|
67
|
+
* @param {object} [opts]
|
|
68
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
|
|
69
|
+
* @param {Record<string, ContainerStage>} [opts.stages] - Declared stages.
|
|
70
|
+
* @param {string|null} [opts.compatibilityMinimum] - The claimed floor.
|
|
71
|
+
* @param {string} [opts.e2eStage] - Which stage the suite runs against.
|
|
72
|
+
* @returns {string|null} The exact build, or `null`.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveFoundryVersion(stage: string, { env, stages, compatibilityMinimum, e2eStage, }?: {
|
|
75
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
76
|
+
stages?: Record<string, ContainerStage> | undefined;
|
|
77
|
+
compatibilityMinimum?: string | null | undefined;
|
|
78
|
+
e2eStage?: string | undefined;
|
|
79
|
+
}): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* The image a run uses.
|
|
82
|
+
*
|
|
83
|
+
* `FOUNDRYVTT_CONTAINER_IMAGE` wins, then `packageBuild.container.image`, then
|
|
84
|
+
* felddy's major tag for whichever version the package already implies — the
|
|
85
|
+
* pinned build, or failing that the compatibility floor. Only a package that
|
|
86
|
+
* claims neither floats.
|
|
87
|
+
*
|
|
88
|
+
* @param {object} [opts]
|
|
89
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
|
|
90
|
+
* @param {string|null} [opts.image] - The configured image.
|
|
91
|
+
* @param {string|null} [opts.version] - The pinned build, if any.
|
|
92
|
+
* @param {string|null} [opts.compatibilityMinimum] - The claimed floor.
|
|
93
|
+
* @returns {string} The image reference.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveImage({ env, image, version, compatibilityMinimum, }?: {
|
|
96
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
97
|
+
image?: string | null | undefined;
|
|
98
|
+
version?: string | null | undefined;
|
|
99
|
+
compatibilityMinimum?: string | null | undefined;
|
|
100
|
+
}): string;
|
|
101
|
+
/**
|
|
102
|
+
* The world a stage auto-launches.
|
|
103
|
+
*
|
|
104
|
+
* `null` leaves `FOUNDRY_WORLD` alone, so whatever the image was given decides.
|
|
105
|
+
* An empty string is a *declared* "never auto-launch" — the shape a stage whose
|
|
106
|
+
* world is managed by hand needs, and distinct from saying nothing.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} stage - The stage name.
|
|
109
|
+
* @param {object} [opts]
|
|
110
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
|
|
111
|
+
* @param {Record<string, ContainerStage>} [opts.stages] - Declared stages.
|
|
112
|
+
* @returns {string|null} The world id, `""`, or `null`.
|
|
113
|
+
*/
|
|
114
|
+
export function resolveWorld(stage: string, { env, stages }?: {
|
|
115
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
116
|
+
stages?: Record<string, ContainerStage> | undefined;
|
|
117
|
+
}): string | null;
|
|
118
|
+
/**
|
|
119
|
+
* A stage's dedicated Foundry licence key, if it has one.
|
|
120
|
+
*
|
|
121
|
+
* Foundry is single-seat, so running a `dev` and a `test` container at once
|
|
122
|
+
* needs two keys. `FOUNDRYVTT_<STAGE>_LICENSE_KEY` dedicates one to a stage,
|
|
123
|
+
* overriding any global `FOUNDRY_LICENSE_KEY` passed through.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} stage - The stage name.
|
|
126
|
+
* @param {NodeJS.ProcessEnv} [env] - Environment to read.
|
|
127
|
+
* @returns {string|null} The key, or `null`.
|
|
128
|
+
*/
|
|
129
|
+
export function resolveLicenseKey(stage: string, env?: NodeJS.ProcessEnv): string | null;
|
|
130
|
+
/**
|
|
131
|
+
* The Foundry data root for a stage, checked for what a bind mount needs.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} stage - The stage name.
|
|
134
|
+
* @param {object} [opts]
|
|
135
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
|
|
136
|
+
* @returns {string} The local path.
|
|
137
|
+
* @throws {Error} When it is unset, or names a remote host.
|
|
138
|
+
*/
|
|
139
|
+
export function resolveDataRoot(stage: string, { env }?: {
|
|
140
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
141
|
+
}): string;
|
|
142
|
+
/**
|
|
143
|
+
* The image's own environment variables, as key/value pairs.
|
|
144
|
+
*
|
|
145
|
+
* `CONTAINER_CACHE` is deliberately withheld: it names a path *inside* the
|
|
146
|
+
* container, and {@link dockerRunArgs} sets it to match a mount it actually
|
|
147
|
+
* makes. A host path from a `.env` file would otherwise reach the image naming
|
|
148
|
+
* a directory that is not there.
|
|
149
|
+
*
|
|
150
|
+
* @param {NodeJS.ProcessEnv} [env] - Environment to read.
|
|
151
|
+
* @returns {[string, string][]} The pairs to pass through.
|
|
152
|
+
*/
|
|
153
|
+
export function passthroughEnv(env?: NodeJS.ProcessEnv): [string, string][];
|
|
154
|
+
/**
|
|
155
|
+
* The full `docker run` argument vector for a stage's container.
|
|
156
|
+
*
|
|
157
|
+
* Order matters at the end: docker takes the **last** `-e` for a repeated key,
|
|
158
|
+
* so the per-stage version, world and licence are appended after the
|
|
159
|
+
* passthrough and win over anything it carried.
|
|
160
|
+
*
|
|
161
|
+
* @param {object} opts
|
|
162
|
+
* @param {string} opts.name - Container name.
|
|
163
|
+
* @param {string} opts.image - Image reference.
|
|
164
|
+
* @param {number} opts.port - Host port to publish.
|
|
165
|
+
* @param {string} opts.dataRoot - Host directory to mount at `/data`.
|
|
166
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to pass through.
|
|
167
|
+
* @param {string|null} [opts.cacheDir] - Host download cache to mount.
|
|
168
|
+
* @param {string|null} [opts.version] - Exact build to pin.
|
|
169
|
+
* @param {string|null} [opts.world] - World to auto-launch; `""` forces none.
|
|
170
|
+
* @param {string|null} [opts.licenseKey] - Dedicated licence key.
|
|
171
|
+
* @returns {string[]} Arguments after `docker`.
|
|
172
|
+
*/
|
|
173
|
+
export function dockerRunArgs({ name, image, port, dataRoot, env, cacheDir, version, world, licenseKey, }: {
|
|
174
|
+
name: string;
|
|
175
|
+
image: string;
|
|
176
|
+
port: number;
|
|
177
|
+
dataRoot: string;
|
|
178
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
179
|
+
cacheDir?: string | null | undefined;
|
|
180
|
+
version?: string | null | undefined;
|
|
181
|
+
world?: string | null | undefined;
|
|
182
|
+
licenseKey?: string | null | undefined;
|
|
183
|
+
}): string[];
|
|
184
|
+
/**
|
|
185
|
+
* Run the `docker` CLI, inheriting stdio.
|
|
186
|
+
*
|
|
187
|
+
* @param {string[]} args - Arguments after `docker`.
|
|
188
|
+
* @returns {number} The exit status.
|
|
189
|
+
* @throws {Error} When `docker` is not on `PATH`.
|
|
190
|
+
*/
|
|
191
|
+
export function runDocker(args: string[]): number;
|
|
192
|
+
/**
|
|
193
|
+
* Run the `docker` CLI and capture its output, tolerating failure.
|
|
194
|
+
*
|
|
195
|
+
* @param {string[]} args - Arguments after `docker`.
|
|
196
|
+
* @returns {string} Trimmed stdout, or `""` when the command failed.
|
|
197
|
+
*/
|
|
198
|
+
export function captureDocker(args: string[]): string;
|
|
199
|
+
/**
|
|
200
|
+
* @param {string} name - Container name.
|
|
201
|
+
* @param {boolean} [runningOnly] - Only count a running container.
|
|
202
|
+
* @returns {boolean} Whether a container with exactly this name is present.
|
|
203
|
+
*/
|
|
204
|
+
export function containerExists(name: string, runningOnly?: boolean): boolean;
|
|
205
|
+
/**
|
|
206
|
+
* The HeroicLands-convention Foundry containers currently running.
|
|
207
|
+
*
|
|
208
|
+
* Used to warn about a single-seat licence clash before a run starts, which is
|
|
209
|
+
* why the filter is the shared `-foundry-` convention rather than one package's
|
|
210
|
+
* prefix: the clash is between *any* two licensed instances, not between two
|
|
211
|
+
* containers of the same package.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} [except] - A container to omit from the list.
|
|
214
|
+
* @returns {string[]} The container names.
|
|
215
|
+
*/
|
|
216
|
+
export function runningFoundryContainers(except?: string): string[];
|
|
217
|
+
/**
|
|
218
|
+
* Remove the data-root lock a container left behind when it did not shut down
|
|
219
|
+
* cleanly (`docker rm -f`, a crash, an OOM).
|
|
220
|
+
*
|
|
221
|
+
* Foundry then refuses to start with "already locked by another process",
|
|
222
|
+
* naming no owner — so a stale lock turns every later boot into a failure that
|
|
223
|
+
* reads like corruption rather than litter.
|
|
224
|
+
*
|
|
225
|
+
* **Only safe while the container is stopped**, which is exactly when every
|
|
226
|
+
* boot path here calls it: with nothing running against the data root, a lock
|
|
227
|
+
* present is by definition stale.
|
|
228
|
+
*
|
|
229
|
+
* @param {string} dataRoot - The Foundry user-data root.
|
|
230
|
+
* @param {(message: string) => void} [log] - Progress reporting.
|
|
231
|
+
*/
|
|
232
|
+
export function clearStaleLock(dataRoot: string, log?: (message: string) => void): void;
|
|
233
|
+
/**
|
|
234
|
+
* Everything a container action needs, resolved from configuration and the
|
|
235
|
+
* environment once.
|
|
236
|
+
*
|
|
237
|
+
* @typedef {object} ResolvedContainer
|
|
238
|
+
* @property {string} stage
|
|
239
|
+
* @property {string} name
|
|
240
|
+
* @property {number} port
|
|
241
|
+
* @property {string} image
|
|
242
|
+
* @property {string} url
|
|
243
|
+
* @property {string|null} version
|
|
244
|
+
* @property {string|null} world
|
|
245
|
+
* @property {string|null} licenseKey
|
|
246
|
+
* @property {string|null} cacheDir
|
|
247
|
+
*/
|
|
248
|
+
/**
|
|
249
|
+
* Resolve a stage's container settings.
|
|
250
|
+
*
|
|
251
|
+
* @param {object} opts
|
|
252
|
+
* @param {string} opts.stage - The stage name.
|
|
253
|
+
* @param {object} opts.config - The resolved package-build configuration.
|
|
254
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
|
|
255
|
+
* @returns {ResolvedContainer} The resolved settings.
|
|
256
|
+
*/
|
|
257
|
+
export function resolveContainer({ stage, config, env }: {
|
|
258
|
+
stage: string;
|
|
259
|
+
config: object;
|
|
260
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
261
|
+
}): ResolvedContainer;
|
|
262
|
+
/**
|
|
263
|
+
* Start a stage's container, creating it when it does not exist yet.
|
|
264
|
+
*
|
|
265
|
+
* @param {ResolvedContainer} container - Resolved settings.
|
|
266
|
+
* @param {object} opts
|
|
267
|
+
* @param {string} opts.dataRoot - Host directory to mount at `/data`.
|
|
268
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to pass through.
|
|
269
|
+
* @param {(message: string) => void} [opts.log] - Progress reporting.
|
|
270
|
+
* @returns {number} The exit status.
|
|
271
|
+
*/
|
|
272
|
+
export function startContainer(container: ResolvedContainer, { dataRoot, env, log }: {
|
|
273
|
+
dataRoot: string;
|
|
274
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
275
|
+
log?: ((message: string) => void) | undefined;
|
|
276
|
+
}): number;
|
|
277
|
+
/**
|
|
278
|
+
* Stop and remove a container, tolerating "not running" and "no such
|
|
279
|
+
* container".
|
|
280
|
+
*
|
|
281
|
+
* @param {string} name - Container name.
|
|
282
|
+
* @param {(message: string) => void} [log] - Progress reporting.
|
|
283
|
+
*/
|
|
284
|
+
export function removeContainer(name: string, log?: (message: string) => void): void;
|
|
285
|
+
/**
|
|
286
|
+
* Perform one container action for a stage.
|
|
287
|
+
*
|
|
288
|
+
* @param {object} opts
|
|
289
|
+
* @param {string} opts.action - One of {@link CONTAINER_ACTIONS}.
|
|
290
|
+
* @param {string} opts.stage - The stage name.
|
|
291
|
+
* @param {object} opts.config - The resolved package-build configuration.
|
|
292
|
+
* @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
|
|
293
|
+
* @param {(message: string) => void} [opts.log] - Progress reporting.
|
|
294
|
+
* @returns {number} The exit status.
|
|
295
|
+
* @throws {Error} On an unknown action, or a stage with no usable data root.
|
|
296
|
+
*/
|
|
297
|
+
export function containerAction({ action, stage, config, env, log, }: {
|
|
298
|
+
action: string;
|
|
299
|
+
stage: string;
|
|
300
|
+
config: object;
|
|
301
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
302
|
+
log?: ((message: string) => void) | undefined;
|
|
303
|
+
}): number;
|
|
304
|
+
/**
|
|
305
|
+
* Host port each conventional stage publishes, chosen so they can run at once.
|
|
306
|
+
*
|
|
307
|
+
* A stage a repository adds of its own declares its port in
|
|
308
|
+
* `packageBuild.container.stages`; these four need no entry because every
|
|
309
|
+
* HeroicLands package deploys to the same four.
|
|
310
|
+
*/
|
|
311
|
+
export const DEFAULT_STAGE_PORTS: Readonly<{
|
|
312
|
+
dev: 30000;
|
|
313
|
+
qa: 30001;
|
|
314
|
+
prod: 30002;
|
|
315
|
+
test: 30003;
|
|
316
|
+
}>;
|
|
317
|
+
/** The port Foundry listens on inside the container. */
|
|
318
|
+
export const CONTAINER_PORT: 30000;
|
|
319
|
+
/**
|
|
320
|
+
* Where a host-provided download cache is mounted.
|
|
321
|
+
*
|
|
322
|
+
* A dedicated mount point rather than a subpath of `/data` keeps the cache
|
|
323
|
+
* independent of the data root, so one cache can serve every stage.
|
|
324
|
+
*/
|
|
325
|
+
export const CACHE_MOUNT: "/container_cache";
|
|
326
|
+
/** What `package-build container` can be asked to do. */
|
|
327
|
+
export const CONTAINER_ACTIONS: readonly string[];
|
|
328
|
+
/**
|
|
329
|
+
* A stage declared in `packageBuild.container.stages`.
|
|
330
|
+
*/
|
|
331
|
+
export type ContainerStage = {
|
|
332
|
+
/**
|
|
333
|
+
* Host port to publish.
|
|
334
|
+
*/
|
|
335
|
+
port: number | null;
|
|
336
|
+
/**
|
|
337
|
+
* World to auto-launch; `""` forces none.
|
|
338
|
+
*/
|
|
339
|
+
world: string | null;
|
|
340
|
+
/**
|
|
341
|
+
* Exact Foundry build to pin.
|
|
342
|
+
*/
|
|
343
|
+
version: string | null;
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Everything a container action needs, resolved from configuration and the
|
|
347
|
+
* environment once.
|
|
348
|
+
*/
|
|
349
|
+
export type ResolvedContainer = {
|
|
350
|
+
stage: string;
|
|
351
|
+
name: string;
|
|
352
|
+
port: number;
|
|
353
|
+
image: string;
|
|
354
|
+
url: string;
|
|
355
|
+
version: string | null;
|
|
356
|
+
world: string | null;
|
|
357
|
+
licenseKey: string | null;
|
|
358
|
+
cacheDir: string | null;
|
|
359
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The roots a set of declared keys uses.
|
|
3
|
+
*
|
|
4
|
+
* Derived rather than configured by default, because a package's roots are
|
|
5
|
+
* already a fact about its localization file — asking for them again is one
|
|
6
|
+
* more thing to state and to get wrong. A repository states them only when it
|
|
7
|
+
* references a root the file does not yet declare at all.
|
|
8
|
+
*
|
|
9
|
+
* @param {Iterable<string>} keys - The declared keys.
|
|
10
|
+
* @returns {string[]} The distinct first segments, in name order.
|
|
11
|
+
*/
|
|
12
|
+
export function keyRootsOf(keys: Iterable<string>): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Read every localization reference out of a script.
|
|
15
|
+
*
|
|
16
|
+
* The **AST**, not the text, because a key named in a JSDoc `@example` is
|
|
17
|
+
* documentation: requiring it to exist would make the guard fail on prose, and
|
|
18
|
+
* counting it as a reference would let a comment keep a dead key alive.
|
|
19
|
+
*
|
|
20
|
+
* TypeScript's parser reads plain JavaScript too, so both go down one path
|
|
21
|
+
* rather than two that drift.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} source - The file's contents.
|
|
24
|
+
* @param {object} options
|
|
25
|
+
* @param {string} options.file - Path to the file, for the findings.
|
|
26
|
+
* @param {readonly string[]} options.roots - The key roots.
|
|
27
|
+
* @returns {ReferenceSet} What the file references.
|
|
28
|
+
*/
|
|
29
|
+
export function collectScriptReferences(source: string, { file, roots }: {
|
|
30
|
+
file: string;
|
|
31
|
+
roots: readonly string[];
|
|
32
|
+
}): ReferenceSet;
|
|
33
|
+
/**
|
|
34
|
+
* Read every localization reference out of a template.
|
|
35
|
+
*
|
|
36
|
+
* A text scan, because a template has no AST worth building for this: its keys
|
|
37
|
+
* sit in `{{localize "…"}}` calls and in helper hashes, and nothing in a `.hbs`
|
|
38
|
+
* file resembles a comment closely enough to mislead one.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} source - The file's contents.
|
|
41
|
+
* @param {object} options
|
|
42
|
+
* @param {string} options.file - Path to the file, for the findings.
|
|
43
|
+
* @param {readonly string[]} options.roots - The key roots.
|
|
44
|
+
* @returns {ReferenceSet} What the template references.
|
|
45
|
+
*/
|
|
46
|
+
export function collectTemplateReferences(source: string, { file, roots }: {
|
|
47
|
+
file: string;
|
|
48
|
+
roots: readonly string[];
|
|
49
|
+
}): ReferenceSet;
|
|
50
|
+
/**
|
|
51
|
+
* Combine reference sets into one.
|
|
52
|
+
*
|
|
53
|
+
* Keys are kept whole — every site that references a missing key is worth
|
|
54
|
+
* naming — while namespaces and patterns are de-duplicated, since neither says
|
|
55
|
+
* anything about where it came from.
|
|
56
|
+
*
|
|
57
|
+
* @param {Iterable<ReferenceSet>} sets - The sets to combine.
|
|
58
|
+
* @returns {ReferenceSet} One set holding all of them.
|
|
59
|
+
*/
|
|
60
|
+
export function mergeReferences(sets: Iterable<ReferenceSet>): ReferenceSet;
|
|
61
|
+
/**
|
|
62
|
+
* Compare what a package declares against what it references.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} options
|
|
65
|
+
* @param {string} options.langSource - The reference localization file's text.
|
|
66
|
+
* @param {string} options.langFile - Its path, for the findings about it.
|
|
67
|
+
* @param {ReferenceSet} options.references - Everything that references it.
|
|
68
|
+
* @param {readonly string[]} [options.retained] - Key prefixes to leave out of
|
|
69
|
+
* the advisory half. Each is a repository's statement that the keys under it
|
|
70
|
+
* are reached in a way no scan can see; the honest fix for an unreferenced
|
|
71
|
+
* key is still to delete it.
|
|
72
|
+
* @param {readonly string[]} [options.roots] - The key roots. Derived from the
|
|
73
|
+
* declared keys when absent.
|
|
74
|
+
* @returns {{findings: CoverageFinding[], unreferenced: CoverageFinding[],
|
|
75
|
+
* stats: object}} What must be fixed, what is merely worth reading, and what
|
|
76
|
+
* the run looked at. The two are separate because they are different
|
|
77
|
+
* questions: one says the package is broken, the other that it carries
|
|
78
|
+
* something nobody could see a use for.
|
|
79
|
+
*/
|
|
80
|
+
export function analyzeCoverage({ langSource, langFile, references, retained, roots, }: {
|
|
81
|
+
langSource: string;
|
|
82
|
+
langFile: string;
|
|
83
|
+
references: ReferenceSet;
|
|
84
|
+
retained?: readonly string[] | undefined;
|
|
85
|
+
roots?: readonly string[] | undefined;
|
|
86
|
+
}): {
|
|
87
|
+
findings: CoverageFinding[];
|
|
88
|
+
unreferenced: CoverageFinding[];
|
|
89
|
+
stats: object;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* One place a key is referenced, and how firmly.
|
|
93
|
+
*/
|
|
94
|
+
export type KeyReference = {
|
|
95
|
+
/**
|
|
96
|
+
* - The localization key, in full.
|
|
97
|
+
*/
|
|
98
|
+
key: string;
|
|
99
|
+
/**
|
|
100
|
+
* - Where it is referenced, relative to the repository
|
|
101
|
+
* root — the reference is the finding's site, not the localization file.
|
|
102
|
+
*/
|
|
103
|
+
file: string;
|
|
104
|
+
/**
|
|
105
|
+
* - 1-based line, when it can be established.
|
|
106
|
+
*/
|
|
107
|
+
line?: number | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* - 1-based column, likewise.
|
|
110
|
+
*/
|
|
111
|
+
column?: number | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* - When true the key must be declared verbatim,
|
|
114
|
+
* even if it happens to be a prefix of keys that are. A *generated* key is
|
|
115
|
+
* minted whole, so keys sitting beneath it do not vouch for it; an ordinary
|
|
116
|
+
* textual reference to a family name does not have that property.
|
|
117
|
+
*/
|
|
118
|
+
exact?: boolean | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* - The verb phrase naming how the key is
|
|
121
|
+
* referenced, for the message: `references` by default, so a contributor of
|
|
122
|
+
* generated keys can say `defineType generates` instead.
|
|
123
|
+
*/
|
|
124
|
+
origin?: string | undefined;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Everything one scan learned about how a file addresses localization.
|
|
128
|
+
*/
|
|
129
|
+
export type ReferenceSet = {
|
|
130
|
+
/**
|
|
131
|
+
* - Concrete keys, each at its site.
|
|
132
|
+
*/
|
|
133
|
+
keys: KeyReference[];
|
|
134
|
+
/**
|
|
135
|
+
* - Prefixes whose leaves are never named in
|
|
136
|
+
* source: a DataModel's `LOCALIZATION_PREFIXES`, the static head of a key
|
|
137
|
+
* built at runtime. A namespace vouches for *itself* being reachable, never
|
|
138
|
+
* for the keys beneath it.
|
|
139
|
+
*/
|
|
140
|
+
namespaces: string[];
|
|
141
|
+
/**
|
|
142
|
+
* - Key shapes a dynamic construction can build,
|
|
143
|
+
* with `*` standing for one segment: `` `SOHL.Month.${i}.label` `` is
|
|
144
|
+
* `SOHL.Month.*.label`, and vouches for exactly what that expression can
|
|
145
|
+
* produce.
|
|
146
|
+
*/
|
|
147
|
+
patterns: string[];
|
|
148
|
+
/**
|
|
149
|
+
* - What the scan could not resolve, in
|
|
150
|
+
* its own words.
|
|
151
|
+
*/
|
|
152
|
+
findings: CoverageFinding[];
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* A finding about coverage.
|
|
156
|
+
*
|
|
157
|
+
* Unlike the rules in {@link module :lang}, these carry their own `file`: one
|
|
158
|
+
* run spans the localization file and every source that references it, so a
|
|
159
|
+
* single path supplied by the caller could not be right for all of them.
|
|
160
|
+
*/
|
|
161
|
+
export type CoverageFinding = {
|
|
162
|
+
/**
|
|
163
|
+
* - Path the finding is about.
|
|
164
|
+
*/
|
|
165
|
+
file: string;
|
|
166
|
+
/**
|
|
167
|
+
* - 1-based line, when known.
|
|
168
|
+
*/
|
|
169
|
+
line?: number | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* - 1-based column, when known.
|
|
172
|
+
*/
|
|
173
|
+
column?: number | undefined;
|
|
174
|
+
/**
|
|
175
|
+
* - How it should be treated.
|
|
176
|
+
*/
|
|
177
|
+
severity: "error" | "warning";
|
|
178
|
+
/**
|
|
179
|
+
* - What is wrong, in one sentence.
|
|
180
|
+
*/
|
|
181
|
+
message: string;
|
|
182
|
+
};
|