@hominis/fireforge 0.15.1 → 0.15.3
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 +39 -3
- package/README.md +76 -3
- package/dist/src/commands/build.js +41 -3
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +49 -0
- package/dist/src/commands/furnace/chrome-doc-templates.js +151 -0
- package/dist/src/commands/furnace/chrome-doc.d.ts +34 -0
- package/dist/src/commands/furnace/chrome-doc.js +168 -0
- package/dist/src/commands/furnace/create-mochikit.d.ts +30 -0
- package/dist/src/commands/furnace/create-mochikit.js +70 -0
- package/dist/src/commands/furnace/create-templates.d.ts +53 -0
- package/dist/src/commands/furnace/create-templates.js +118 -0
- package/dist/src/commands/furnace/create-xpcshell.d.ts +27 -0
- package/dist/src/commands/furnace/create-xpcshell.js +53 -0
- package/dist/src/commands/furnace/create.d.ts +17 -0
- package/dist/src/commands/furnace/create.js +59 -12
- package/dist/src/commands/furnace/index.d.ts +2 -1
- package/dist/src/commands/furnace/index.js +20 -2
- package/dist/src/commands/lint.d.ts +13 -1
- package/dist/src/commands/lint.js +33 -7
- package/dist/src/commands/setup.d.ts +1 -1
- package/dist/src/commands/setup.js +3 -2
- package/dist/src/core/build-audit.d.ts +46 -0
- package/dist/src/core/build-audit.js +251 -0
- package/dist/src/core/build-baseline.d.ts +59 -0
- package/dist/src/core/build-baseline.js +83 -0
- package/dist/src/core/build-prepare.d.ts +20 -1
- package/dist/src/core/build-prepare.js +94 -4
- package/dist/src/core/furnace-apply-helpers.d.ts +1 -1
- package/dist/src/core/furnace-config-tokens.d.ts +6 -0
- package/dist/src/core/furnace-config-tokens.js +15 -0
- package/dist/src/core/furnace-config.js +10 -4
- package/dist/src/core/furnace-operation.d.ts +2 -1
- package/dist/src/core/furnace-operation.js +13 -7
- package/dist/src/core/furnace-registration-ast.d.ts +2 -2
- package/dist/src/core/furnace-registration-ast.js +1 -1
- package/dist/src/core/furnace-validate-compatibility.js +18 -7
- package/dist/src/core/furnace-validate-helpers.d.ts +31 -1
- package/dist/src/core/furnace-validate-helpers.js +101 -18
- package/dist/src/core/furnace-validate-registration.d.ts +1 -1
- package/dist/src/core/furnace-validate-registration.js +1 -1
- package/dist/src/core/mach-error-hints.d.ts +29 -0
- package/dist/src/core/mach-error-hints.js +43 -0
- package/dist/src/core/mach.d.ts +5 -2
- package/dist/src/core/mach.js +31 -4
- package/dist/src/core/marionette-preflight.d.ts +14 -7
- package/dist/src/core/marionette-preflight.js +94 -44
- package/dist/src/core/patch-lint-cross.d.ts +1 -1
- package/dist/src/core/patch-lint-cross.js +1 -1
- package/dist/src/core/patch-lint-diff-tag.d.ts +33 -0
- package/dist/src/core/patch-lint-diff-tag.js +83 -0
- package/dist/src/core/patch-lint.js +29 -9
- package/dist/src/types/commands/options.d.ts +25 -0
- package/dist/src/types/commands/patches.d.ts +9 -0
- package/dist/src/types/config.d.ts +1 -1
- package/dist/src/types/furnace.d.ts +13 -2
- package/package.json +1 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* `fireforge furnace chrome-doc create <name>` — scaffolds a top-level
|
|
4
|
+
* chrome document (xhtml + js + css + ftl + jar.mn registrations).
|
|
5
|
+
*
|
|
6
|
+
* Motivation: `furnace create` covers custom elements under
|
|
7
|
+
* `toolkit/content/widgets/`, but top-level chrome documents (the
|
|
8
|
+
* `mybrowser.xhtml`-class entry points a fork adds alongside or instead
|
|
9
|
+
* of `browser.xhtml`) are today hand-authored with error-prone jar.mn +
|
|
10
|
+
* jar.inc.mn + locales/jar.mn glue. The `*` preprocessor flag, the
|
|
11
|
+
* macOS titlebar-button carve-out, the startup-topic observer, and the
|
|
12
|
+
* Fluent linkage each have silent-break failure modes.
|
|
13
|
+
*
|
|
14
|
+
* This command writes the four source files and appends three jar.mn
|
|
15
|
+
* entries under a rollback journal identical in shape to `furnace create`.
|
|
16
|
+
* A SIGINT mid-scaffold restores every touched file; a successful run
|
|
17
|
+
* leaves the tree ready for `fireforge build`.
|
|
18
|
+
*/
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { loadConfig } from '../../core/config.js';
|
|
21
|
+
import { runFurnaceMutation } from '../../core/furnace-operation.js';
|
|
22
|
+
import { createRollbackJournal, recordCreatedDir, restoreRollbackJournalOrThrow, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
23
|
+
import { DEFAULT_LICENSE, getLicenseHeader } from '../../core/license-headers.js';
|
|
24
|
+
import { InvalidArgumentError } from '../../errors/base.js';
|
|
25
|
+
import { FurnaceError } from '../../errors/furnace.js';
|
|
26
|
+
import { pathExists, readText, writeText } from '../../utils/fs.js';
|
|
27
|
+
import { intro, note, outro } from '../../utils/logger.js';
|
|
28
|
+
import { generateChromeDocCss, generateChromeDocFtl, generateChromeDocJs, generateChromeDocXhtml, jarIncMnEntryForChromeDoc, jarMnEntriesForChromeDoc, localeJarMnEntryForChromeDoc, } from './chrome-doc-templates.js';
|
|
29
|
+
/** Chrome-doc name shape: lowercase ASCII, optional hyphens, no leading digit. */
|
|
30
|
+
const CHROME_DOC_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
31
|
+
/**
|
|
32
|
+
* Validates a chrome-doc name. Lowercase ASCII, optional hyphens, no
|
|
33
|
+
* leading digit — the name is used verbatim in CSS selectors, jar.mn
|
|
34
|
+
* entries, FTL keys, and file basenames, so anything outside that
|
|
35
|
+
* character set would break at least one downstream consumer.
|
|
36
|
+
* @param name Chrome-doc name (file basename without extension).
|
|
37
|
+
* @throws InvalidArgumentError when the name is unusable.
|
|
38
|
+
*/
|
|
39
|
+
function validateChromeDocName(name) {
|
|
40
|
+
if (!name.trim()) {
|
|
41
|
+
throw new InvalidArgumentError('Chrome-doc name is required', 'name');
|
|
42
|
+
}
|
|
43
|
+
if (!CHROME_DOC_NAME_PATTERN.test(name)) {
|
|
44
|
+
throw new InvalidArgumentError('Chrome-doc name must be lowercase ASCII, may contain hyphens, and must not start with a digit (e.g. mybrowser, about-onboarding).', 'name');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Appends a line to a jar.mn-style file when that exact line is not
|
|
49
|
+
* already present. Captures the pre-write contents in the journal so a
|
|
50
|
+
* mid-run interruption restores the file to its original state.
|
|
51
|
+
*/
|
|
52
|
+
async function appendJarEntryIfAbsent(filePath, entry, journal) {
|
|
53
|
+
if (!(await pathExists(filePath))) {
|
|
54
|
+
// Target jar.mn doesn't exist in this tree layout. We do NOT create it
|
|
55
|
+
// — a fork that moved the jar file needs the operator to choose a
|
|
56
|
+
// placement. The command surfaces this as a FurnaceError so the user
|
|
57
|
+
// can investigate rather than silently writing to a non-canonical path.
|
|
58
|
+
throw new FurnaceError(`Required jar file ${filePath} does not exist; cannot register chrome-doc entry. Check that the fork's engine layout matches the expected browser/ and locales/ tree.`);
|
|
59
|
+
}
|
|
60
|
+
const existing = await readText(filePath);
|
|
61
|
+
if (existing.includes(entry)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
await snapshotFile(journal, filePath);
|
|
65
|
+
const withEntry = existing.trimEnd() + '\n' + entry + '\n';
|
|
66
|
+
await writeText(filePath, withEntry);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Writes the xhtml/js/css/ftl source files plus the three jar.mn
|
|
70
|
+
* registrations under a rollback journal. Any interruption leaves the
|
|
71
|
+
* tree in its pre-command state.
|
|
72
|
+
*/
|
|
73
|
+
async function performChromeDocMutations(args) {
|
|
74
|
+
const journal = createRollbackJournal();
|
|
75
|
+
args.operationContext.registerJournal(journal);
|
|
76
|
+
// XHTML uses an inline XML comment since getLicenseHeader has no XML
|
|
77
|
+
// style — the SPDX convention is a single-line comment at the top.
|
|
78
|
+
const jsHeader = getLicenseHeader(args.license, 'js');
|
|
79
|
+
const cssHeader = getLicenseHeader(args.license, 'css');
|
|
80
|
+
const ftlHeader = getLicenseHeader(args.license, 'hash');
|
|
81
|
+
const written = [];
|
|
82
|
+
try {
|
|
83
|
+
const contentDir = join(args.engineDir, 'browser/base/content');
|
|
84
|
+
const sharedThemeDir = join(args.engineDir, 'browser/themes/shared');
|
|
85
|
+
const localeDir = join(args.engineDir, 'browser/locales/en-US/browser');
|
|
86
|
+
for (const dir of [contentDir, sharedThemeDir, localeDir]) {
|
|
87
|
+
if (!(await pathExists(dir))) {
|
|
88
|
+
recordCreatedDir(journal, dir);
|
|
89
|
+
const { ensureDir } = await import('../../utils/fs.js');
|
|
90
|
+
await ensureDir(dir);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const xhtmlPath = join(contentDir, `${args.name}.xhtml`);
|
|
94
|
+
if (await pathExists(xhtmlPath)) {
|
|
95
|
+
throw new FurnaceError(`${args.name}.xhtml already exists at ${xhtmlPath}. Remove it or choose a different name.`);
|
|
96
|
+
}
|
|
97
|
+
await snapshotFile(journal, xhtmlPath);
|
|
98
|
+
await writeText(xhtmlPath, generateChromeDocXhtml(args.name, args.withTitlebar, args.license));
|
|
99
|
+
written.push(`browser/base/content/${args.name}.xhtml`);
|
|
100
|
+
const jsPath = join(contentDir, `${args.name}.js`);
|
|
101
|
+
await snapshotFile(journal, jsPath);
|
|
102
|
+
await writeText(jsPath, generateChromeDocJs(args.name, jsHeader));
|
|
103
|
+
written.push(`browser/base/content/${args.name}.js`);
|
|
104
|
+
const cssPath = join(sharedThemeDir, `${args.name}-chrome.css`);
|
|
105
|
+
await snapshotFile(journal, cssPath);
|
|
106
|
+
await writeText(cssPath, generateChromeDocCss(args.name, args.withTitlebar, cssHeader));
|
|
107
|
+
written.push(`browser/themes/shared/${args.name}-chrome.css`);
|
|
108
|
+
const ftlPath = join(localeDir, `${args.name}.ftl`);
|
|
109
|
+
await snapshotFile(journal, ftlPath);
|
|
110
|
+
await writeText(ftlPath, generateChromeDocFtl(args.name, ftlHeader));
|
|
111
|
+
written.push(`browser/locales/en-US/browser/${args.name}.ftl`);
|
|
112
|
+
// jar.mn registrations — XHTML + JS go through the `*` preprocessor
|
|
113
|
+
// for brand substitution, CSS goes through jar.inc.mn, FTL through
|
|
114
|
+
// the locale jar.
|
|
115
|
+
const jarMnPath = join(args.engineDir, 'browser/base/jar.mn');
|
|
116
|
+
for (const entry of jarMnEntriesForChromeDoc(args.name)) {
|
|
117
|
+
await appendJarEntryIfAbsent(jarMnPath, entry, journal);
|
|
118
|
+
}
|
|
119
|
+
written.push('browser/base/jar.mn');
|
|
120
|
+
const jarIncMnPath = join(args.engineDir, 'browser/themes/shared/jar.inc.mn');
|
|
121
|
+
await appendJarEntryIfAbsent(jarIncMnPath, jarIncMnEntryForChromeDoc(args.name), journal);
|
|
122
|
+
written.push('browser/themes/shared/jar.inc.mn');
|
|
123
|
+
const localeJarMnPath = join(args.engineDir, 'browser/locales/jar.mn');
|
|
124
|
+
await appendJarEntryIfAbsent(localeJarMnPath, localeJarMnEntryForChromeDoc(args.name), journal);
|
|
125
|
+
written.push('browser/locales/jar.mn');
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
await restoreRollbackJournalOrThrow(journal, `Failed to scaffold chrome-doc "${args.name}"`);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
return written;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Runs `furnace chrome-doc create <name>`.
|
|
135
|
+
* @param projectRoot Root directory of the project.
|
|
136
|
+
* @param name Chrome-doc name (e.g. `mybrowser`, `aboutonboarding`).
|
|
137
|
+
* @param options CLI-provided options.
|
|
138
|
+
*/
|
|
139
|
+
export async function furnaceChromeDocCreateCommand(projectRoot, name, options = {}) {
|
|
140
|
+
intro('Furnace chrome-doc create');
|
|
141
|
+
validateChromeDocName(name);
|
|
142
|
+
const forgeConfig = await loadConfig(projectRoot);
|
|
143
|
+
const license = forgeConfig.license ?? DEFAULT_LICENSE;
|
|
144
|
+
const engineDir = join(projectRoot, 'engine');
|
|
145
|
+
if (!(await pathExists(engineDir))) {
|
|
146
|
+
throw new FurnaceError('Engine directory not found. Run "fireforge download" first to scaffold a chrome-doc.');
|
|
147
|
+
}
|
|
148
|
+
const withTitlebar = options.titlebar ?? true;
|
|
149
|
+
const written = await runFurnaceMutation(projectRoot, 'chrome-doc-rollback', (ctx) => performChromeDocMutations({
|
|
150
|
+
name,
|
|
151
|
+
license,
|
|
152
|
+
engineDir,
|
|
153
|
+
withTitlebar,
|
|
154
|
+
operationContext: ctx,
|
|
155
|
+
}));
|
|
156
|
+
note([
|
|
157
|
+
`Chrome document "${name}" scaffolded:`,
|
|
158
|
+
...written.map((f) => ` engine/${f}`),
|
|
159
|
+
'',
|
|
160
|
+
'Next steps:',
|
|
161
|
+
` 1. Edit engine/browser/base/content/${name}.xhtml and fill in the body.`,
|
|
162
|
+
` 2. Localize strings in engine/browser/locales/en-US/browser/${name}.ftl.`,
|
|
163
|
+
` 3. Run "fireforge build" to validate packaging (post-build audit will flag`,
|
|
164
|
+
' any entry whose file does not land in the dist bundle).',
|
|
165
|
+
].join('\n'), name);
|
|
166
|
+
outro('Chrome document created');
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=chrome-doc.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MochiKit (chrome://mochikit) test-harness scaffolder for
|
|
3
|
+
* `fireforge furnace create --test-style=mochikit`.
|
|
4
|
+
*
|
|
5
|
+
* Motivation: browser-chrome mochitests require a `tabbrowser` to exist in
|
|
6
|
+
* the top-level chrome document. Forks with a bespoke chrome document
|
|
7
|
+
* (e.g. `mybrowser.xhtml`) that deliberately omits tabbrowser cannot run
|
|
8
|
+
* browser-chrome tests today. MochiKit tests load the component module
|
|
9
|
+
* directly via `chrome://global/` and assert against `customElements`, so
|
|
10
|
+
* they work against any fork that registers the upstream toolkit test
|
|
11
|
+
* manifest tree — including those without a tabbrowser.
|
|
12
|
+
*/
|
|
13
|
+
import { type RollbackJournal } from '../../core/furnace-rollback.js';
|
|
14
|
+
import type { ProjectLicense } from '../../types/config.js';
|
|
15
|
+
/**
|
|
16
|
+
* Scaffolds a MochiKit test for a newly created custom component under
|
|
17
|
+
* `engine/toolkit/content/tests/widgets/`. Mirrors the layout stock
|
|
18
|
+
* Firefox widgets (moz-button, moz-toggle, etc.) use, so an operator who
|
|
19
|
+
* already added the `widgets/` tree to their test-manifest registration
|
|
20
|
+
* picks the new test up automatically.
|
|
21
|
+
*
|
|
22
|
+
* Appends a per-test entry to the existing `chrome.toml` when present,
|
|
23
|
+
* writes a fresh `[DEFAULT]`-headed one otherwise. The caller is still
|
|
24
|
+
* responsible for ensuring the `toolkit/content/tests/widgets/chrome.toml`
|
|
25
|
+
* path is registered somewhere in the moz.build tree; most forks inherit
|
|
26
|
+
* this from upstream via `TEST_HARNESS_FILES += [...]`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function scaffoldMochikitTestFiles(componentName: string, license: ProjectLicense, paths: {
|
|
29
|
+
engine: string;
|
|
30
|
+
}, journal?: RollbackJournal): Promise<string[]>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* MochiKit (chrome://mochikit) test-harness scaffolder for
|
|
4
|
+
* `fireforge furnace create --test-style=mochikit`.
|
|
5
|
+
*
|
|
6
|
+
* Motivation: browser-chrome mochitests require a `tabbrowser` to exist in
|
|
7
|
+
* the top-level chrome document. Forks with a bespoke chrome document
|
|
8
|
+
* (e.g. `mybrowser.xhtml`) that deliberately omits tabbrowser cannot run
|
|
9
|
+
* browser-chrome tests today. MochiKit tests load the component module
|
|
10
|
+
* directly via `chrome://global/` and assert against `customElements`, so
|
|
11
|
+
* they work against any fork that registers the upstream toolkit test
|
|
12
|
+
* manifest tree — including those without a tabbrowser.
|
|
13
|
+
*/
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
16
|
+
import { getLicenseHeader } from '../../core/license-headers.js';
|
|
17
|
+
import { ensureDir, pathExists, readText, writeText } from '../../utils/fs.js';
|
|
18
|
+
import { warn } from '../../utils/logger.js';
|
|
19
|
+
import { generateMochikitChromeTomlEntry, generateMochikitChromeTomlSkeleton, generateMochikitTestContent, mochikitTestFileName, } from './create-templates.js';
|
|
20
|
+
/**
|
|
21
|
+
* Scaffolds a MochiKit test for a newly created custom component under
|
|
22
|
+
* `engine/toolkit/content/tests/widgets/`. Mirrors the layout stock
|
|
23
|
+
* Firefox widgets (moz-button, moz-toggle, etc.) use, so an operator who
|
|
24
|
+
* already added the `widgets/` tree to their test-manifest registration
|
|
25
|
+
* picks the new test up automatically.
|
|
26
|
+
*
|
|
27
|
+
* Appends a per-test entry to the existing `chrome.toml` when present,
|
|
28
|
+
* writes a fresh `[DEFAULT]`-headed one otherwise. The caller is still
|
|
29
|
+
* responsible for ensuring the `toolkit/content/tests/widgets/chrome.toml`
|
|
30
|
+
* path is registered somewhere in the moz.build tree; most forks inherit
|
|
31
|
+
* this from upstream via `TEST_HARNESS_FILES += [...]`.
|
|
32
|
+
*/
|
|
33
|
+
export async function scaffoldMochikitTestFiles(componentName, license, paths, journal) {
|
|
34
|
+
const testDir = join(paths.engine, 'toolkit/content/tests/widgets');
|
|
35
|
+
if (journal && !(await pathExists(testDir))) {
|
|
36
|
+
recordCreatedDir(journal, testDir);
|
|
37
|
+
}
|
|
38
|
+
await ensureDir(testDir);
|
|
39
|
+
const hashHeader = getLicenseHeader(license, 'hash');
|
|
40
|
+
const writtenFiles = [];
|
|
41
|
+
const testFileName = mochikitTestFileName(componentName);
|
|
42
|
+
const testFilePath = join(testDir, testFileName);
|
|
43
|
+
if (journal)
|
|
44
|
+
await snapshotFile(journal, testFilePath);
|
|
45
|
+
await writeText(testFilePath, generateMochikitTestContent(componentName));
|
|
46
|
+
writtenFiles.push(testFileName);
|
|
47
|
+
// chrome.toml — append entry if the file already exists, otherwise write
|
|
48
|
+
// a fresh skeleton + entry. Idempotency: if the entry is already present
|
|
49
|
+
// the manifest is left untouched so re-runs don't double-register.
|
|
50
|
+
const manifestPath = join(testDir, 'chrome.toml');
|
|
51
|
+
const entry = generateMochikitChromeTomlEntry(componentName);
|
|
52
|
+
if (await pathExists(manifestPath)) {
|
|
53
|
+
const existing = await readText(manifestPath);
|
|
54
|
+
if (!existing.includes(`["${testFileName}"]`)) {
|
|
55
|
+
if (journal)
|
|
56
|
+
await snapshotFile(journal, manifestPath);
|
|
57
|
+
await writeText(manifestPath, existing.trimEnd() + '\n\n' + entry);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
if (journal)
|
|
62
|
+
await snapshotFile(journal, manifestPath);
|
|
63
|
+
await writeText(manifestPath, generateMochikitChromeTomlSkeleton(hashHeader) + entry);
|
|
64
|
+
writtenFiles.push('chrome.toml');
|
|
65
|
+
}
|
|
66
|
+
warn(`MochiKit scaffold written under toolkit/content/tests/widgets/. ` +
|
|
67
|
+
'Ensure `toolkit/content/tests/widgets/chrome.toml` is reachable from an existing test-harness registration (upstream TEST_HARNESS_FILES entries handle this by default).');
|
|
68
|
+
return writtenFiles;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=create-mochikit.js.map
|
|
@@ -24,3 +24,56 @@ export declare function generateMjsContent(name: string, className: string, desc
|
|
|
24
24
|
export declare function generateCssContent(header: string): string;
|
|
25
25
|
/** Generates the .ftl file content for a custom component. */
|
|
26
26
|
export declare function generateFtlContent(name: string, header: string): string;
|
|
27
|
+
/** Returns the canonical xpcshell test file basename for a component. */
|
|
28
|
+
export declare function xpcshellTestFileName(name: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Generates an xpcshell test file for a custom component.
|
|
31
|
+
*
|
|
32
|
+
* xpcshell tests run headless without a `tabbrowser`, so they suit
|
|
33
|
+
* storage/observer/module-loading code in forks that do not mount the
|
|
34
|
+
* upstream browser chrome (and therefore lack `openLinkIn` →
|
|
35
|
+
* `URILoadingHelper`). The scaffold imports the component module via
|
|
36
|
+
* `ChromeUtils.importESModule` and asserts the module resolves — enough
|
|
37
|
+
* to catch registration regressions without touching DOM rendering paths
|
|
38
|
+
* that xpcshell cannot execute.
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateXpcshellTestContent(name: string, header: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Generates an `xpcshell.toml` manifest for a custom component's test
|
|
43
|
+
* directory. Kept minimal — adding prefs, head.js, and support-files is
|
|
44
|
+
* left to the operator because those decisions depend on what the
|
|
45
|
+
* component actually touches (Services.storage, observer topics, etc.).
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateXpcshellManifestContent(name: string, header: string): string;
|
|
48
|
+
/** Returns the canonical mochikit test file basename for a component. */
|
|
49
|
+
export declare function mochikitTestFileName(name: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Generates a MochiKit (chrome://mochikit) test for a custom component.
|
|
52
|
+
*
|
|
53
|
+
* MochiKit tests load the component module directly via the global chrome
|
|
54
|
+
* URI and assert that `customElements.get(<tag>)` returns a constructor.
|
|
55
|
+
* They run on forks whose top-level chrome document lacks a `tabbrowser`
|
|
56
|
+
* (the class of bug that forces `--xpcshell` for storage code) because
|
|
57
|
+
* they do not traverse `URILoadingHelper.openLinkIn`.
|
|
58
|
+
*
|
|
59
|
+
* The scaffold here is a smoke test — the component is defined and the
|
|
60
|
+
* constructor is a function. Real UI assertions (render output, l10n
|
|
61
|
+
* wiring, keyboard interactions) are intentionally left out because they
|
|
62
|
+
* depend on the component's shape; operators can extend the test using
|
|
63
|
+
* the same SimpleTest APIs upstream toolkit widgets (moz-button, etc.)
|
|
64
|
+
* rely on.
|
|
65
|
+
*/
|
|
66
|
+
export declare function generateMochikitTestContent(name: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Generates the `chrome.toml` entry block to append for a newly scaffolded
|
|
69
|
+
* mochikit test. When the manifest already exists the caller appends this
|
|
70
|
+
* snippet; when absent, the caller writes a file that starts with a
|
|
71
|
+
* `[DEFAULT]` stanza followed by this block.
|
|
72
|
+
*/
|
|
73
|
+
export declare function generateMochikitChromeTomlEntry(name: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Generates the minimal `chrome.toml` used when the file does not yet
|
|
76
|
+
* exist in the tree. Keeps the `[DEFAULT]` stanza empty so each scaffold
|
|
77
|
+
* adds its own per-test entry, matching the stock Firefox convention.
|
|
78
|
+
*/
|
|
79
|
+
export declare function generateMochikitChromeTomlSkeleton(header: string): string;
|
|
@@ -81,6 +81,124 @@ export function generateFtlContent(name, header) {
|
|
|
81
81
|
return `${header}
|
|
82
82
|
|
|
83
83
|
## Strings for the ${name} component
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
/** Returns the canonical xpcshell test file basename for a component. */
|
|
87
|
+
export function xpcshellTestFileName(name) {
|
|
88
|
+
return `test_${name.replace(/-/g, '_')}_module_loads.js`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Generates an xpcshell test file for a custom component.
|
|
92
|
+
*
|
|
93
|
+
* xpcshell tests run headless without a `tabbrowser`, so they suit
|
|
94
|
+
* storage/observer/module-loading code in forks that do not mount the
|
|
95
|
+
* upstream browser chrome (and therefore lack `openLinkIn` →
|
|
96
|
+
* `URILoadingHelper`). The scaffold imports the component module via
|
|
97
|
+
* `ChromeUtils.importESModule` and asserts the module resolves — enough
|
|
98
|
+
* to catch registration regressions without touching DOM rendering paths
|
|
99
|
+
* that xpcshell cannot execute.
|
|
100
|
+
*/
|
|
101
|
+
export function generateXpcshellTestContent(name, header) {
|
|
102
|
+
return `${header}
|
|
103
|
+
|
|
104
|
+
"use strict";
|
|
105
|
+
|
|
106
|
+
add_task(async function test_${name.replace(/-/g, '_')}_module_loads() {
|
|
107
|
+
// Module-load smoke check: resolves the ESM at its registered chrome URI.
|
|
108
|
+
// Replace or extend with storage-layer assertions as the component grows
|
|
109
|
+
// (Services.storage, observer topics, JSONFile, etc. are all available
|
|
110
|
+
// here without a tabbrowser).
|
|
111
|
+
const moduleUri = "chrome://global/content/elements/${name}.mjs";
|
|
112
|
+
const module = await ChromeUtils.importESModule(moduleUri);
|
|
113
|
+
Assert.ok(
|
|
114
|
+
module,
|
|
115
|
+
"${name}.mjs should load under xpcshell (storage-layer code path).",
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Generates an `xpcshell.toml` manifest for a custom component's test
|
|
122
|
+
* directory. Kept minimal — adding prefs, head.js, and support-files is
|
|
123
|
+
* left to the operator because those decisions depend on what the
|
|
124
|
+
* component actually touches (Services.storage, observer topics, etc.).
|
|
125
|
+
*/
|
|
126
|
+
export function generateXpcshellManifestContent(name, header) {
|
|
127
|
+
return `${header}
|
|
128
|
+
|
|
129
|
+
[DEFAULT]
|
|
130
|
+
head = ""
|
|
131
|
+
|
|
132
|
+
["${xpcshellTestFileName(name)}"]
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
/** Returns the canonical mochikit test file basename for a component. */
|
|
136
|
+
export function mochikitTestFileName(name) {
|
|
137
|
+
return `test_${name}.html`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generates a MochiKit (chrome://mochikit) test for a custom component.
|
|
141
|
+
*
|
|
142
|
+
* MochiKit tests load the component module directly via the global chrome
|
|
143
|
+
* URI and assert that `customElements.get(<tag>)` returns a constructor.
|
|
144
|
+
* They run on forks whose top-level chrome document lacks a `tabbrowser`
|
|
145
|
+
* (the class of bug that forces `--xpcshell` for storage code) because
|
|
146
|
+
* they do not traverse `URILoadingHelper.openLinkIn`.
|
|
147
|
+
*
|
|
148
|
+
* The scaffold here is a smoke test — the component is defined and the
|
|
149
|
+
* constructor is a function. Real UI assertions (render output, l10n
|
|
150
|
+
* wiring, keyboard interactions) are intentionally left out because they
|
|
151
|
+
* depend on the component's shape; operators can extend the test using
|
|
152
|
+
* the same SimpleTest APIs upstream toolkit widgets (moz-button, etc.)
|
|
153
|
+
* rely on.
|
|
154
|
+
*/
|
|
155
|
+
export function generateMochikitTestContent(name) {
|
|
156
|
+
return `<!DOCTYPE html>
|
|
157
|
+
<html>
|
|
158
|
+
<head>
|
|
159
|
+
<meta charset="utf-8" />
|
|
160
|
+
<title>Test the ${name} custom element</title>
|
|
161
|
+
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
|
162
|
+
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
|
163
|
+
</head>
|
|
164
|
+
<body>
|
|
165
|
+
<p id="display"></p>
|
|
166
|
+
<div id="content" style="display: none"></div>
|
|
167
|
+
<pre id="test"></pre>
|
|
168
|
+
<script type="module">
|
|
169
|
+
import "chrome://global/content/elements/${name}.mjs";
|
|
170
|
+
|
|
171
|
+
SimpleTest.waitForExplicitFinish();
|
|
172
|
+
|
|
173
|
+
add_task(async function test_${name.replace(/-/g, '_')}_defined() {
|
|
174
|
+
const ctor = await customElements.whenDefined("${name}");
|
|
175
|
+
ok(ctor, "${name} custom element should be defined");
|
|
176
|
+
is(typeof ctor, "function", "Constructor should be a function");
|
|
177
|
+
});
|
|
178
|
+
</script>
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Generates the `chrome.toml` entry block to append for a newly scaffolded
|
|
185
|
+
* mochikit test. When the manifest already exists the caller appends this
|
|
186
|
+
* snippet; when absent, the caller writes a file that starts with a
|
|
187
|
+
* `[DEFAULT]` stanza followed by this block.
|
|
188
|
+
*/
|
|
189
|
+
export function generateMochikitChromeTomlEntry(name) {
|
|
190
|
+
return `["${mochikitTestFileName(name)}"]\n`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Generates the minimal `chrome.toml` used when the file does not yet
|
|
194
|
+
* exist in the tree. Keeps the `[DEFAULT]` stanza empty so each scaffold
|
|
195
|
+
* adds its own per-test entry, matching the stock Firefox convention.
|
|
196
|
+
*/
|
|
197
|
+
export function generateMochikitChromeTomlSkeleton(header) {
|
|
198
|
+
return `${header}
|
|
199
|
+
|
|
200
|
+
[DEFAULT]
|
|
201
|
+
|
|
84
202
|
`;
|
|
85
203
|
}
|
|
86
204
|
//# sourceMappingURL=create-templates.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xpcshell test-harness scaffolder for `fireforge furnace create --xpcshell`.
|
|
3
|
+
* Extracted from `create.ts` so the command entrypoint stays under the
|
|
4
|
+
* per-file LOC budget and the scaffolder is unit-testable in isolation.
|
|
5
|
+
*/
|
|
6
|
+
import { type RollbackJournal } from '../../core/furnace-rollback.js';
|
|
7
|
+
import type { ProjectLicense } from '../../types/config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Scaffolds an xpcshell test harness for a newly created custom component.
|
|
10
|
+
*
|
|
11
|
+
* xpcshell is the appropriate harness for storage-layer code on forks
|
|
12
|
+
* without a `tabbrowser` (no `openLinkIn` → `URILoadingHelper`). Browser
|
|
13
|
+
* chrome mochitests require tabbrowser; xpcshell does not, so storage,
|
|
14
|
+
* observers, and ESM-loading logic can be covered headless.
|
|
15
|
+
*
|
|
16
|
+
* Writes `test_<name>_module_loads.js` and an `xpcshell.toml` manifest
|
|
17
|
+
* into `engine/browser/base/content/test/<binary-name>-xpcshell/
|
|
18
|
+
* <component-name>/`. moz.build registration is intentionally left to the
|
|
19
|
+
* operator — wiring an `XPCSHELL_TESTS_MANIFESTS` entry requires a
|
|
20
|
+
* deliberate choice about which moz.build should own it, and an
|
|
21
|
+
* auto-insertion that guessed wrong would be worse than a note.
|
|
22
|
+
*/
|
|
23
|
+
export declare function scaffoldXpcshellTestFiles(componentName: string, license: ProjectLicense, forgeConfig: {
|
|
24
|
+
binaryName: string;
|
|
25
|
+
}, paths: {
|
|
26
|
+
engine: string;
|
|
27
|
+
}, journal?: RollbackJournal): Promise<string[]>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* xpcshell test-harness scaffolder for `fireforge furnace create --xpcshell`.
|
|
4
|
+
* Extracted from `create.ts` so the command entrypoint stays under the
|
|
5
|
+
* per-file LOC budget and the scaffolder is unit-testable in isolation.
|
|
6
|
+
*/
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { recordCreatedDir, snapshotFile, } from '../../core/furnace-rollback.js';
|
|
9
|
+
import { getLicenseHeader } from '../../core/license-headers.js';
|
|
10
|
+
import { ensureDir, pathExists, writeText } from '../../utils/fs.js';
|
|
11
|
+
import { warn } from '../../utils/logger.js';
|
|
12
|
+
import { generateXpcshellManifestContent, generateXpcshellTestContent, xpcshellTestFileName, } from './create-templates.js';
|
|
13
|
+
/**
|
|
14
|
+
* Scaffolds an xpcshell test harness for a newly created custom component.
|
|
15
|
+
*
|
|
16
|
+
* xpcshell is the appropriate harness for storage-layer code on forks
|
|
17
|
+
* without a `tabbrowser` (no `openLinkIn` → `URILoadingHelper`). Browser
|
|
18
|
+
* chrome mochitests require tabbrowser; xpcshell does not, so storage,
|
|
19
|
+
* observers, and ESM-loading logic can be covered headless.
|
|
20
|
+
*
|
|
21
|
+
* Writes `test_<name>_module_loads.js` and an `xpcshell.toml` manifest
|
|
22
|
+
* into `engine/browser/base/content/test/<binary-name>-xpcshell/
|
|
23
|
+
* <component-name>/`. moz.build registration is intentionally left to the
|
|
24
|
+
* operator — wiring an `XPCSHELL_TESTS_MANIFESTS` entry requires a
|
|
25
|
+
* deliberate choice about which moz.build should own it, and an
|
|
26
|
+
* auto-insertion that guessed wrong would be worse than a note.
|
|
27
|
+
*/
|
|
28
|
+
export async function scaffoldXpcshellTestFiles(componentName, license, forgeConfig, paths, journal) {
|
|
29
|
+
const parentDirName = `${forgeConfig.binaryName}-xpcshell`;
|
|
30
|
+
const testDir = join(paths.engine, 'browser/base/content/test', parentDirName, componentName);
|
|
31
|
+
if (journal && !(await pathExists(testDir))) {
|
|
32
|
+
recordCreatedDir(journal, testDir);
|
|
33
|
+
}
|
|
34
|
+
await ensureDir(testDir);
|
|
35
|
+
const jsHeader = getLicenseHeader(license, 'js');
|
|
36
|
+
const hashHeader = getLicenseHeader(license, 'hash');
|
|
37
|
+
const testFiles = [];
|
|
38
|
+
const testFileName = xpcshellTestFileName(componentName);
|
|
39
|
+
const testFilePath = join(testDir, testFileName);
|
|
40
|
+
if (journal)
|
|
41
|
+
await snapshotFile(journal, testFilePath);
|
|
42
|
+
await writeText(testFilePath, generateXpcshellTestContent(componentName, jsHeader));
|
|
43
|
+
testFiles.push(testFileName);
|
|
44
|
+
const manifestPath = join(testDir, 'xpcshell.toml');
|
|
45
|
+
if (journal)
|
|
46
|
+
await snapshotFile(journal, manifestPath);
|
|
47
|
+
await writeText(manifestPath, generateXpcshellManifestContent(componentName, hashHeader));
|
|
48
|
+
testFiles.push('xpcshell.toml');
|
|
49
|
+
warn(`xpcshell scaffold written under browser/base/content/test/${parentDirName}/${componentName}/. ` +
|
|
50
|
+
'Add the directory to XPCSHELL_TESTS_MANIFESTS in the nearest moz.build to run it via "fireforge test".');
|
|
51
|
+
return testFiles;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=create-xpcshell.js.map
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import type { FurnaceCreateOptions } from '../../types/commands/index.js';
|
|
2
|
+
/** Resolved test-harness selection for a `furnace create` run. */
|
|
3
|
+
export type ResolvedTestStyle = 'mochikit' | 'browser-chrome' | 'xpcshell' | 'none';
|
|
4
|
+
/**
|
|
5
|
+
* Collapses `--with-tests`, `--xpcshell`, and `--test-style` into the single
|
|
6
|
+
* scaffold dispatch used inside the mutation phase.
|
|
7
|
+
*
|
|
8
|
+
* Backwards-compat invariants:
|
|
9
|
+
* - `--xpcshell` alone is equivalent to `--test-style=xpcshell`.
|
|
10
|
+
* - `--with-tests` alone (no `--test-style`) now defaults to `mochikit`
|
|
11
|
+
* (previously it defaulted to browser-chrome; the dogfooding pass
|
|
12
|
+
* flagged browser-chrome as unrunnable against non-tabbrowser chrome).
|
|
13
|
+
* Operators who need the old behavior can pass
|
|
14
|
+
* `--with-tests --test-style=browser-chrome`.
|
|
15
|
+
* - `--xpcshell --with-tests` is rejected as ambiguous.
|
|
16
|
+
* @throws InvalidArgumentError when flags conflict.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveTestStyle(options: FurnaceCreateOptions): ResolvedTestStyle;
|
|
2
19
|
/**
|
|
3
20
|
* Runs the furnace create command to scaffold a new custom component.
|
|
4
21
|
* @param projectRoot - Root directory of the project
|