@heroiclands/package-build 0.5.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.
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Hash a password the way Foundry's `core/auth.mjs` does: pbkdf2, 1000 rounds,
3
+ * 64 bytes, sha512. A different shape logs nobody in.
4
+ *
5
+ * @param {string} password - The plaintext password.
6
+ * @param {string} salt - The hex salt.
7
+ * @returns {string} The hex hash.
8
+ */
9
+ export function hashPassword(password: string, salt: string): string;
10
+ /**
11
+ * The disposable world's identity and credentials.
12
+ *
13
+ * @typedef {object} E2EWorld
14
+ * @property {string} worldId
15
+ * @property {string} worldTitle
16
+ * @property {string} worldDescription
17
+ * @property {string} gmId
18
+ * @property {string} gmName
19
+ * @property {string} gmPassword
20
+ */
21
+ /**
22
+ * Resolve the seeded world from configuration and the environment.
23
+ *
24
+ * Everything derives from the package id unless the repository says otherwise,
25
+ * and the environment overrides under the repository's own variable prefix —
26
+ * the same prefix the deploy already uses — so a contributor can point a run at
27
+ * a scratch world without touching committed configuration.
28
+ *
29
+ * @param {object} config - The resolved package-build configuration.
30
+ * @param {NodeJS.ProcessEnv} [env] - Environment to read.
31
+ * @returns {E2EWorld} The resolved world.
32
+ */
33
+ export function resolveE2EWorld(config: object, env?: NodeJS.ProcessEnv): E2EWorld;
34
+ /**
35
+ * The `world.json` a seeded world carries.
36
+ *
37
+ * Foundry re-stamps `coreVersion`, `systemVersion` and `compatibility` from the
38
+ * running core on launch, so a generation-level range is enough here — the
39
+ * point is a world that launches without a migration prompt or a setup step.
40
+ *
41
+ * @param {object} opts
42
+ * @param {string} opts.worldId
43
+ * @param {string} opts.worldTitle
44
+ * @param {string} opts.worldDescription
45
+ * @param {string} opts.systemId - The system the world runs.
46
+ * @param {string} opts.systemVersion - That system's version.
47
+ * @param {string} opts.coreVersion - The Foundry generation.
48
+ * @returns {object} The world manifest.
49
+ */
50
+ export function worldManifest({ worldId, worldTitle, worldDescription, systemId, systemVersion, coreVersion, }: {
51
+ worldId: string;
52
+ worldTitle: string;
53
+ worldDescription: string;
54
+ systemId: string;
55
+ systemVersion: string;
56
+ coreVersion: string;
57
+ }): object;
58
+ /**
59
+ * The seeded world's single Gamemaster.
60
+ *
61
+ * @param {object} opts
62
+ * @param {string} opts.id - The fixed document id.
63
+ * @param {string} opts.name - The user name.
64
+ * @param {string} opts.password - The plaintext password.
65
+ * @param {string} opts.salt - The hex salt to hash it with.
66
+ * @returns {object} The user document.
67
+ */
68
+ export function gmDocument({ id, name, password, salt }: {
69
+ id: string;
70
+ name: string;
71
+ password: string;
72
+ salt: string;
73
+ }): object;
74
+ /**
75
+ * The one pre-activated scene every seeded world carries.
76
+ *
77
+ * @returns {object} The scene document.
78
+ */
79
+ export function defaultSceneDocument(): object;
80
+ /**
81
+ * The world setting that switches a module package on.
82
+ *
83
+ * A **system** is the world's own — `world.json` names it and Foundry loads it.
84
+ * A **module** is not: it has to be activated, and the switch is
85
+ * `core.moduleConfiguration`, a world setting holding a JSON map of package id
86
+ * to enabled. Without it a module repository would stand its suite up against a
87
+ * world that never loaded the very thing under test.
88
+ *
89
+ * @param {string} packageId - The module to enable.
90
+ * @returns {object} The setting document.
91
+ */
92
+ export function moduleConfigurationDocument(packageId: string): object;
93
+ /**
94
+ * Whether a `/join` response shows a world that is actually **active**.
95
+ *
96
+ * Foundry answers on the port long before a world is serving; a suite started
97
+ * at that moment fails every spec for no visible reason. The join screen
98
+ * renders the form only once a world is active, so that is what is waited for.
99
+ *
100
+ * @param {string} body - The response body.
101
+ * @returns {boolean} Whether the world is active.
102
+ */
103
+ export function isWorldActive(body: string): boolean;
104
+ /**
105
+ * The build a sweep was asked to run against.
106
+ *
107
+ * There is no default, deliberately. The product of a sweep is a citable result
108
+ * — "the full suite passed on 14.367" — and a hard-coded "newest release" would
109
+ * rot on Foundry's next release day, quietly turning the sweep into a second
110
+ * pinned build. A bare major or a tag is refused for the same reason: the image
111
+ * passes it through verbatim, so the run would name no particular Foundry.
112
+ *
113
+ * @param {string[]} argv - Arguments after the action.
114
+ * @returns {string} The exact build, trimmed.
115
+ * @throws {Error} When none was given, or it is not an exact build.
116
+ */
117
+ export function resolveSweepVersion(argv: string[]): string;
118
+ /**
119
+ * What a fast loop was asked to do.
120
+ *
121
+ * @typedef {object} FastArgs
122
+ * @property {string[]} targets Build targets, in declared order.
123
+ * @property {boolean} recreate Whether the container must be recreated.
124
+ * @property {boolean} runSuite Whether to run the suite at all.
125
+ * @property {string[]} suiteArgs Arguments handed to the suite verbatim.
126
+ */
127
+ /**
128
+ * Parse the fast loop's arguments against the repository's build table.
129
+ *
130
+ * Build order is **declaration order**, not the order they were asked for: a
131
+ * bundler that empties the stage has to run before the passes that copy into
132
+ * it, and the repository already stated that by writing them down in order.
133
+ *
134
+ * @param {string[]} argv - Arguments after the action.
135
+ * @param {Record<string, {script: string, recreate: boolean}>} build - The
136
+ * declared build table.
137
+ * @returns {FastArgs} What to do.
138
+ * @throws {Error} On an unknown build target.
139
+ */
140
+ export function parseFastArgs(argv: string[], build: Record<string, {
141
+ script: string;
142
+ recreate: boolean;
143
+ }>): FastArgs;
144
+ /**
145
+ * Seed the disposable world into the end-to-end stage's data root.
146
+ *
147
+ * Writes `world.json` and compiles each LevelDB collection from JSON, so the
148
+ * result is a genuine world Foundry launches without migration or setup. The
149
+ * world directory is wiped and rewritten each time, which is what makes a run
150
+ * repeatable.
151
+ *
152
+ * @param {object} opts
153
+ * @param {object} opts.config - The resolved package-build configuration.
154
+ * @param {object} opts.packageJson - The repository's `package.json`.
155
+ * @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
156
+ * @param {(message: string) => void} [opts.log] - Progress reporting.
157
+ * @returns {Promise<{worldDir: string, world: E2EWorld}>} Where it landed.
158
+ */
159
+ export function seedTestWorld({ config, packageJson, env, log, }: {
160
+ config: object;
161
+ packageJson: object;
162
+ env?: NodeJS.ProcessEnv | undefined;
163
+ log?: ((message: string) => void) | undefined;
164
+ }): Promise<{
165
+ worldDir: string;
166
+ world: E2EWorld;
167
+ }>;
168
+ /**
169
+ * Poll until the world is active, or fail with a diagnosis.
170
+ *
171
+ * A licence failure never recovers, so it is detected from the container's own
172
+ * log and reported at once rather than after a three-minute timeout that says
173
+ * nothing about why.
174
+ *
175
+ * @param {object} opts
176
+ * @param {string} opts.url - The container's base URL.
177
+ * @param {string} opts.container - The container name, for its log.
178
+ * @param {string} opts.stage - The stage, named in a licence failure.
179
+ * @param {number} [opts.timeoutMs] - How long to wait.
180
+ * @param {(message: string) => void} [opts.log] - Progress reporting.
181
+ * @returns {Promise<void>} Resolves once the world is active.
182
+ * @throws {Error} On a licence failure or a timeout.
183
+ */
184
+ export function waitForWorld({ url, container, stage, timeoutMs, log, }: {
185
+ url: string;
186
+ container: string;
187
+ stage: string;
188
+ timeoutMs?: number | undefined;
189
+ log?: ((message: string) => void) | undefined;
190
+ }): Promise<void>;
191
+ /**
192
+ * Run the repository's suite.
193
+ *
194
+ * `ELECTRON_RUN_AS_NODE` is stripped from the child environment. Editor
195
+ * terminals and most agent shells export it, and with it set an Electron-based
196
+ * runner launches as plain Node, rejects its own flags, and dies with a
197
+ * `MODULE_NOT_FOUND` naming nothing relevant.
198
+ *
199
+ * @param {object} opts
200
+ * @param {string[]} opts.command - The program and its arguments.
201
+ * @param {string[]} [opts.args] - Extra arguments, appended verbatim.
202
+ * @param {string} opts.cwd - The repository root.
203
+ * @param {NodeJS.ProcessEnv} [opts.env] - Environment for the child.
204
+ * @param {(message: string) => void} [opts.log] - Progress reporting.
205
+ * @returns {number} The suite's exit status.
206
+ */
207
+ export function runSuite({ command, args, cwd, env, log, }: {
208
+ command: string[];
209
+ args?: string[] | undefined;
210
+ cwd: string;
211
+ env?: NodeJS.ProcessEnv | undefined;
212
+ log?: ((message: string) => void) | undefined;
213
+ }): number;
214
+ /**
215
+ * The suite command for a mode, or a clear failure when none is declared.
216
+ *
217
+ * @param {object} config - The resolved package-build configuration.
218
+ * @param {"run"|"open"} mode - Which command to take.
219
+ * @returns {string[]} The program and its arguments.
220
+ * @throws {Error} When the repository declares no such command.
221
+ */
222
+ export function suiteCommand(config: object, mode: "run" | "open"): string[];
223
+ /**
224
+ * A full, from-scratch end-to-end run.
225
+ *
226
+ * Deploy the staged package, reseed the world, recreate the container onto it,
227
+ * wait for it to activate, run the suite, and tear the container down again —
228
+ * except in interactive mode, where it is left serving.
229
+ *
230
+ * This is the only path that may change Foundry build: the seeded world is
231
+ * stamped with the build that created it, and Foundry refuses to auto-launch a
232
+ * world stamped by another.
233
+ *
234
+ * @param {object} opts
235
+ * @param {object} opts.config - The resolved package-build configuration.
236
+ * @param {object} opts.packageJson - The repository's `package.json`.
237
+ * @param {"run"|"open"} [opts.mode] - Headless or interactive.
238
+ * @param {string[]} [opts.suiteArgs] - Extra arguments for the suite.
239
+ * @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
240
+ * @param {(message: string) => void} [opts.log] - Progress reporting.
241
+ * @returns {Promise<number>} The suite's exit status.
242
+ */
243
+ export function e2eRun({ config, packageJson, mode, suiteArgs, env, log, }: {
244
+ config: object;
245
+ packageJson: object;
246
+ mode?: "open" | "run" | undefined;
247
+ suiteArgs?: string[] | undefined;
248
+ env?: NodeJS.ProcessEnv | undefined;
249
+ log?: ((message: string) => void) | undefined;
250
+ }): Promise<number>;
251
+ /**
252
+ * The iteration loop: rebuild what changed, redeploy, cycle, re-run.
253
+ *
254
+ * Each step has a quiet failure mode, and hand-rolling the sequence means
255
+ * meeting them one at a time. The bundler empties the stage, so build order is
256
+ * the declared one; the deploy is a destructive mirror, so it runs on a
257
+ * complete stage; a running Foundry holds its packs open, so the world is
258
+ * always cycled; the container answers on its port long before the world is
259
+ * serving, so the loop waits for the world.
260
+ *
261
+ * @param {object} opts
262
+ * @param {object} opts.config - The resolved package-build configuration.
263
+ * @param {string[]} [opts.argv] - Arguments after the action.
264
+ * @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
265
+ * @param {(message: string) => void} [opts.log] - Progress reporting.
266
+ * @returns {Promise<number>} The suite's exit status.
267
+ */
268
+ export function e2eFast({ config, argv, env, log, }: {
269
+ config: object;
270
+ argv?: string[] | undefined;
271
+ env?: NodeJS.ProcessEnv | undefined;
272
+ log?: ((message: string) => void) | undefined;
273
+ }): Promise<number>;
274
+ /**
275
+ * The forward sweep: the full suite against a build the repository does not
276
+ * pin.
277
+ *
278
+ * Routine runs go against the pinned build, which is the manifest's
279
+ * `compatibility.minimum` — the claim the suite exists to defend. That leaves
280
+ * the other direction untested: a new Foundry release can break the package and
281
+ * nothing would notice until a user did.
282
+ *
283
+ * A green sweep is what licenses moving `compatibility.verified` to that build.
284
+ * A red one is the early warning it exists to produce.
285
+ *
286
+ * @param {object} opts
287
+ * @param {object} opts.config - The resolved package-build configuration.
288
+ * @param {object} opts.packageJson - The repository's `package.json`.
289
+ * @param {string[]} [opts.argv] - Arguments after the action.
290
+ * @param {NodeJS.ProcessEnv} [opts.env] - Environment to read.
291
+ * @param {(message: string) => void} [opts.log] - Progress reporting.
292
+ * @returns {Promise<number>} The suite's exit status.
293
+ */
294
+ export function e2eSweep({ config, packageJson, argv, env, log, }: {
295
+ config: object;
296
+ packageJson: object;
297
+ argv?: string[] | undefined;
298
+ env?: NodeJS.ProcessEnv | undefined;
299
+ log?: ((message: string) => void) | undefined;
300
+ }): Promise<number>;
301
+ /**
302
+ * The Gamemaster's document id in every seeded world.
303
+ *
304
+ * Fixed, and deliberately not derived from the package: a spec has to know it
305
+ * without a handoff from the seed, and the world is disposable, so there is
306
+ * nothing for a per-package id to disambiguate. Sixteen alphanumeric
307
+ * characters, which is the shape Foundry document ids take.
308
+ */
309
+ export const E2E_GM_ID: "heroiclandsE2EGM";
310
+ /**
311
+ * The seeded, pre-activated default scene's id.
312
+ *
313
+ * A non-empty world means Foundry's New User Experience does not auto-start the
314
+ * welcome tour, whose callout overlays sheets; an *active* scene at load means
315
+ * the canvas is ready, which several read paths depend on.
316
+ */
317
+ export const E2E_SCENE_ID: "heroiclandsScene";
318
+ /** How the suite may be run. */
319
+ export const E2E_MODES: readonly string[];
320
+ /**
321
+ * The disposable world's identity and credentials.
322
+ */
323
+ export type E2EWorld = {
324
+ worldId: string;
325
+ worldTitle: string;
326
+ worldDescription: string;
327
+ gmId: string;
328
+ gmName: string;
329
+ gmPassword: string;
330
+ };
331
+ /**
332
+ * What a fast loop was asked to do.
333
+ */
334
+ export type FastArgs = {
335
+ /**
336
+ * Build targets, in declared order.
337
+ */
338
+ targets: string[];
339
+ /**
340
+ * Whether the container must be recreated.
341
+ */
342
+ recreate: boolean;
343
+ /**
344
+ * Whether to run the suite at all.
345
+ */
346
+ runSuite: boolean;
347
+ /**
348
+ * Arguments handed to the suite verbatim.
349
+ */
350
+ suiteArgs: string[];
351
+ };
package/types/index.d.mts CHANGED
@@ -3,4 +3,8 @@ export * as bundle from "./bundle.mjs";
3
3
  export * as stage from "./stage.mjs";
4
4
  export * as release from "./release.mjs";
5
5
  export * as deploy from "./deploy.mjs";
6
+ export * as container from "./container.mjs";
7
+ export * as e2e from "./e2e.mjs";
6
8
  export * as lang from "./lang.mjs";
9
+ export * as coverage from "./coverage.mjs";
10
+ export * as templates from "./templates.mjs";
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Every user-visible literal a template leaves untranslated.
3
+ *
4
+ * @param {string} source - The template source.
5
+ * @param {object} [options]
6
+ * @param {Iterable<string>} [options.allow] - Literals that are deliberately
7
+ * not localization keys — a code sample shown as a placeholder, say. This is
8
+ * the escape hatch, not the rule: anything that is ordinary UI prose belongs
9
+ * in the localization file, and a repository states each entry with the
10
+ * reason it cannot be one.
11
+ * @returns {TemplateFinding[]} The findings, in the order they appear.
12
+ */
13
+ export function findHardcodedText(source: string, { allow }?: {
14
+ allow?: Iterable<string> | undefined;
15
+ }): TemplateFinding[];
16
+ /**
17
+ * Whether the template compiles at all.
18
+ *
19
+ * Precompiling rather than compiling: the question is whether Handlebars can
20
+ * *parse* the source, and precompilation answers it without needing any of the
21
+ * helpers the template calls to exist.
22
+ *
23
+ * @param {string} source - The template source.
24
+ * @returns {TemplateFinding[]} One finding when it does not parse, else none.
25
+ */
26
+ export function findTemplateSyntaxErrors(source: string): TemplateFinding[];
27
+ /**
28
+ * A single finding, in the fields the shared diagnostic format takes.
29
+ *
30
+ * `file` is absent for the same reason it is absent from a localization
31
+ * finding: these functions are handed source text, not a path.
32
+ *
33
+ * @typedef {object} TemplateFinding
34
+ * @property {number} [line] - 1-based line, omitted when it cannot be
35
+ * established honestly.
36
+ * @property {number} [column] - 1-based column, omitted likewise.
37
+ * @property {"error"|"warning"} severity - How the finding should be treated.
38
+ * @property {string} message - What is wrong, in one sentence.
39
+ */
40
+ /**
41
+ * Attributes whose value the user reads.
42
+ *
43
+ * Every one of these renders as prose somewhere — a tooltip, a placeholder, a
44
+ * screen reader's announcement — so English in one is as untranslated as
45
+ * English in a heading, and far easier to miss.
46
+ *
47
+ * @type {readonly string[]}
48
+ */
49
+ export const VISIBLE_ATTRIBUTES: readonly string[];
50
+ /**
51
+ * A single finding, in the fields the shared diagnostic format takes.
52
+ *
53
+ * `file` is absent for the same reason it is absent from a localization
54
+ * finding: these functions are handed source text, not a path.
55
+ */
56
+ export type TemplateFinding = {
57
+ /**
58
+ * - 1-based line, omitted when it cannot be
59
+ * established honestly.
60
+ */
61
+ line?: number | undefined;
62
+ /**
63
+ * - 1-based column, omitted likewise.
64
+ */
65
+ column?: number | undefined;
66
+ /**
67
+ * - How the finding should be treated.
68
+ */
69
+ severity: "error" | "warning";
70
+ /**
71
+ * - What is wrong, in one sentence.
72
+ */
73
+ message: string;
74
+ };