@hominis/fireforge 0.9.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 +7 -0
- package/LICENSE.md +294 -0
- package/README.md +435 -0
- package/dist/bin/fireforge.d.ts +10 -0
- package/dist/bin/fireforge.js +29 -0
- package/dist/src/cli.d.ts +33 -0
- package/dist/src/cli.js +180 -0
- package/dist/src/commands/bootstrap.d.ts +9 -0
- package/dist/src/commands/bootstrap.js +73 -0
- package/dist/src/commands/build.d.ts +11 -0
- package/dist/src/commands/build.js +102 -0
- package/dist/src/commands/config.d.ts +13 -0
- package/dist/src/commands/config.js +135 -0
- package/dist/src/commands/discard.d.ts +12 -0
- package/dist/src/commands/discard.js +84 -0
- package/dist/src/commands/doctor.d.ts +18 -0
- package/dist/src/commands/doctor.js +356 -0
- package/dist/src/commands/download.d.ts +11 -0
- package/dist/src/commands/download.js +127 -0
- package/dist/src/commands/export-all.d.ts +11 -0
- package/dist/src/commands/export-all.js +122 -0
- package/dist/src/commands/export-shared.d.ts +48 -0
- package/dist/src/commands/export-shared.js +208 -0
- package/dist/src/commands/export.d.ts +13 -0
- package/dist/src/commands/export.js +178 -0
- package/dist/src/commands/furnace/apply.d.ts +7 -0
- package/dist/src/commands/furnace/apply.js +80 -0
- package/dist/src/commands/furnace/create.d.ts +8 -0
- package/dist/src/commands/furnace/create.js +377 -0
- package/dist/src/commands/furnace/deploy.d.ts +8 -0
- package/dist/src/commands/furnace/deploy.js +338 -0
- package/dist/src/commands/furnace/diff.d.ts +7 -0
- package/dist/src/commands/furnace/diff.js +119 -0
- package/dist/src/commands/furnace/index.d.ts +16 -0
- package/dist/src/commands/furnace/index.js +121 -0
- package/dist/src/commands/furnace/list.d.ts +5 -0
- package/dist/src/commands/furnace/list.js +65 -0
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +188 -0
- package/dist/src/commands/furnace/preview.d.ts +7 -0
- package/dist/src/commands/furnace/preview.js +96 -0
- package/dist/src/commands/furnace/remove.d.ts +8 -0
- package/dist/src/commands/furnace/remove.js +159 -0
- package/dist/src/commands/furnace/scan.d.ts +5 -0
- package/dist/src/commands/furnace/scan.js +112 -0
- package/dist/src/commands/furnace/status.d.ts +7 -0
- package/dist/src/commands/furnace/status.js +137 -0
- package/dist/src/commands/furnace/validate.d.ts +6 -0
- package/dist/src/commands/furnace/validate.js +91 -0
- package/dist/src/commands/furnace/validation-output.d.ts +7 -0
- package/dist/src/commands/furnace/validation-output.js +22 -0
- package/dist/src/commands/import.d.ts +11 -0
- package/dist/src/commands/import.js +241 -0
- package/dist/src/commands/lint.d.ts +10 -0
- package/dist/src/commands/lint.js +118 -0
- package/dist/src/commands/package.d.ts +11 -0
- package/dist/src/commands/package.js +80 -0
- package/dist/src/commands/re-export.d.ts +12 -0
- package/dist/src/commands/re-export.js +242 -0
- package/dist/src/commands/rebase/abort.d.ts +7 -0
- package/dist/src/commands/rebase/abort.js +49 -0
- package/dist/src/commands/rebase/confirm.d.ts +18 -0
- package/dist/src/commands/rebase/confirm.js +33 -0
- package/dist/src/commands/rebase/continue.d.ts +7 -0
- package/dist/src/commands/rebase/continue.js +81 -0
- package/dist/src/commands/rebase/index.d.ts +22 -0
- package/dist/src/commands/rebase/index.js +127 -0
- package/dist/src/commands/rebase/patch-loop.d.ts +9 -0
- package/dist/src/commands/rebase/patch-loop.js +135 -0
- package/dist/src/commands/rebase/summary.d.ts +12 -0
- package/dist/src/commands/rebase/summary.js +43 -0
- package/dist/src/commands/rebase.d.ts +4 -0
- package/dist/src/commands/rebase.js +6 -0
- package/dist/src/commands/register.d.ts +13 -0
- package/dist/src/commands/register.js +67 -0
- package/dist/src/commands/reset.d.ts +11 -0
- package/dist/src/commands/reset.js +83 -0
- package/dist/src/commands/resolve.d.ts +9 -0
- package/dist/src/commands/resolve.js +124 -0
- package/dist/src/commands/run.d.ts +9 -0
- package/dist/src/commands/run.js +91 -0
- package/dist/src/commands/setup-support.d.ts +23 -0
- package/dist/src/commands/setup-support.js +310 -0
- package/dist/src/commands/setup.d.ts +11 -0
- package/dist/src/commands/setup.js +94 -0
- package/dist/src/commands/status.d.ts +11 -0
- package/dist/src/commands/status.js +268 -0
- package/dist/src/commands/test.d.ts +12 -0
- package/dist/src/commands/test.js +182 -0
- package/dist/src/commands/token-coverage.d.ts +5 -0
- package/dist/src/commands/token-coverage.js +57 -0
- package/dist/src/commands/token.d.ts +14 -0
- package/dist/src/commands/token.js +121 -0
- package/dist/src/commands/watch.d.ts +9 -0
- package/dist/src/commands/watch.js +112 -0
- package/dist/src/commands/wire.d.ts +13 -0
- package/dist/src/commands/wire.js +149 -0
- package/dist/src/core/ast-utils.d.ts +47 -0
- package/dist/src/core/ast-utils.js +57 -0
- package/dist/src/core/brand-validation.d.ts +7 -0
- package/dist/src/core/brand-validation.js +15 -0
- package/dist/src/core/branding.d.ts +49 -0
- package/dist/src/core/branding.js +229 -0
- package/dist/src/core/browser-wire.d.ts +40 -0
- package/dist/src/core/browser-wire.js +66 -0
- package/dist/src/core/build-prepare.d.ts +25 -0
- package/dist/src/core/build-prepare.js +93 -0
- package/dist/src/core/config-mutate.d.ts +15 -0
- package/dist/src/core/config-mutate.js +51 -0
- package/dist/src/core/config-paths.d.ts +28 -0
- package/dist/src/core/config-paths.js +65 -0
- package/dist/src/core/config-state.d.ts +28 -0
- package/dist/src/core/config-state.js +152 -0
- package/dist/src/core/config-validate.d.ts +11 -0
- package/dist/src/core/config-validate.js +141 -0
- package/dist/src/core/config.d.ts +39 -0
- package/dist/src/core/config.js +70 -0
- package/dist/src/core/file-lock.d.ts +11 -0
- package/dist/src/core/file-lock.js +80 -0
- package/dist/src/core/firefox-archive.d.ts +40 -0
- package/dist/src/core/firefox-archive.js +63 -0
- package/dist/src/core/firefox-cache.d.ts +23 -0
- package/dist/src/core/firefox-cache.js +134 -0
- package/dist/src/core/firefox-download.d.ts +21 -0
- package/dist/src/core/firefox-download.js +129 -0
- package/dist/src/core/firefox-extract.d.ts +21 -0
- package/dist/src/core/firefox-extract.js +53 -0
- package/dist/src/core/firefox.d.ts +34 -0
- package/dist/src/core/firefox.js +78 -0
- package/dist/src/core/furnace-apply-helpers.d.ts +21 -0
- package/dist/src/core/furnace-apply-helpers.js +244 -0
- package/dist/src/core/furnace-apply.d.ts +16 -0
- package/dist/src/core/furnace-apply.js +147 -0
- package/dist/src/core/furnace-config.d.ts +94 -0
- package/dist/src/core/furnace-config.js +372 -0
- package/dist/src/core/furnace-constants.d.ts +4 -0
- package/dist/src/core/furnace-constants.js +6 -0
- package/dist/src/core/furnace-registration-ast.d.ts +24 -0
- package/dist/src/core/furnace-registration-ast.js +218 -0
- package/dist/src/core/furnace-registration-remove.d.ts +14 -0
- package/dist/src/core/furnace-registration-remove.js +89 -0
- package/dist/src/core/furnace-registration-validate.d.ts +20 -0
- package/dist/src/core/furnace-registration-validate.js +40 -0
- package/dist/src/core/furnace-registration.d.ts +29 -0
- package/dist/src/core/furnace-registration.js +96 -0
- package/dist/src/core/furnace-rollback.d.ts +20 -0
- package/dist/src/core/furnace-rollback.js +66 -0
- package/dist/src/core/furnace-scanner.d.ts +40 -0
- package/dist/src/core/furnace-scanner.js +143 -0
- package/dist/src/core/furnace-stories.d.ts +37 -0
- package/dist/src/core/furnace-stories.js +185 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-accessibility.js +32 -0
- package/dist/src/core/furnace-validate-checks.d.ts +4 -0
- package/dist/src/core/furnace-validate-checks.js +7 -0
- package/dist/src/core/furnace-validate-compatibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-compatibility.js +57 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +28 -0
- package/dist/src/core/furnace-validate-helpers.js +129 -0
- package/dist/src/core/furnace-validate-registration.d.ts +37 -0
- package/dist/src/core/furnace-validate-registration.js +220 -0
- package/dist/src/core/furnace-validate-structure.d.ts +6 -0
- package/dist/src/core/furnace-validate-structure.js +66 -0
- package/dist/src/core/furnace-validate.d.ts +16 -0
- package/dist/src/core/furnace-validate.js +103 -0
- package/dist/src/core/git-base.d.ts +47 -0
- package/dist/src/core/git-base.js +50 -0
- package/dist/src/core/git-diff.d.ts +63 -0
- package/dist/src/core/git-diff.js +246 -0
- package/dist/src/core/git-file-ops.d.ts +65 -0
- package/dist/src/core/git-file-ops.js +141 -0
- package/dist/src/core/git-status.d.ts +65 -0
- package/dist/src/core/git-status.js +163 -0
- package/dist/src/core/git.d.ts +113 -0
- package/dist/src/core/git.js +363 -0
- package/dist/src/core/license-headers.d.ts +36 -0
- package/dist/src/core/license-headers.js +83 -0
- package/dist/src/core/mach-build-artifacts.d.ts +29 -0
- package/dist/src/core/mach-build-artifacts.js +117 -0
- package/dist/src/core/mach-mozconfig.d.ts +17 -0
- package/dist/src/core/mach-mozconfig.js +50 -0
- package/dist/src/core/mach-python.d.ts +16 -0
- package/dist/src/core/mach-python.js +126 -0
- package/dist/src/core/mach.d.ts +106 -0
- package/dist/src/core/mach.js +166 -0
- package/dist/src/core/manifest-helpers.d.ts +25 -0
- package/dist/src/core/manifest-helpers.js +96 -0
- package/dist/src/core/manifest-register.d.ts +30 -0
- package/dist/src/core/manifest-register.js +65 -0
- package/dist/src/core/manifest-rules.d.ts +39 -0
- package/dist/src/core/manifest-rules.js +151 -0
- package/dist/src/core/manifest-tokenizers.d.ts +34 -0
- package/dist/src/core/manifest-tokenizers.js +84 -0
- package/dist/src/core/parser-fallback.d.ts +36 -0
- package/dist/src/core/parser-fallback.js +43 -0
- package/dist/src/core/patch-apply-fuzz.d.ts +29 -0
- package/dist/src/core/patch-apply-fuzz.js +70 -0
- package/dist/src/core/patch-apply.d.ts +46 -0
- package/dist/src/core/patch-apply.js +235 -0
- package/dist/src/core/patch-export.d.ts +99 -0
- package/dist/src/core/patch-export.js +314 -0
- package/dist/src/core/patch-files.d.ts +11 -0
- package/dist/src/core/patch-files.js +51 -0
- package/dist/src/core/patch-lint.d.ts +72 -0
- package/dist/src/core/patch-lint.js +403 -0
- package/dist/src/core/patch-lock.d.ts +8 -0
- package/dist/src/core/patch-lock.js +29 -0
- package/dist/src/core/patch-manifest-consistency.d.ts +24 -0
- package/dist/src/core/patch-manifest-consistency.js +135 -0
- package/dist/src/core/patch-manifest-io.d.ts +36 -0
- package/dist/src/core/patch-manifest-io.js +77 -0
- package/dist/src/core/patch-manifest-query.d.ts +48 -0
- package/dist/src/core/patch-manifest-query.js +124 -0
- package/dist/src/core/patch-manifest-validate.d.ts +22 -0
- package/dist/src/core/patch-manifest-validate.js +72 -0
- package/dist/src/core/patch-manifest.d.ts +11 -0
- package/dist/src/core/patch-manifest.js +12 -0
- package/dist/src/core/patch-parse.d.ts +43 -0
- package/dist/src/core/patch-parse.js +143 -0
- package/dist/src/core/patch-transform.d.ts +21 -0
- package/dist/src/core/patch-transform.js +138 -0
- package/dist/src/core/rebase-session.d.ts +47 -0
- package/dist/src/core/rebase-session.js +65 -0
- package/dist/src/core/register-browser-content.d.ts +11 -0
- package/dist/src/core/register-browser-content.js +116 -0
- package/dist/src/core/register-module.d.ts +11 -0
- package/dist/src/core/register-module.js +76 -0
- package/dist/src/core/register-shared-css.d.ts +11 -0
- package/dist/src/core/register-shared-css.js +117 -0
- package/dist/src/core/register-test-manifest.d.ts +18 -0
- package/dist/src/core/register-test-manifest.js +99 -0
- package/dist/src/core/state-file.d.ts +4 -0
- package/dist/src/core/state-file.js +25 -0
- package/dist/src/core/token-coverage.d.ts +12 -0
- package/dist/src/core/token-coverage.js +74 -0
- package/dist/src/core/token-manager.d.ts +55 -0
- package/dist/src/core/token-manager.js +387 -0
- package/dist/src/core/wire-destroy.d.ts +21 -0
- package/dist/src/core/wire-destroy.js +103 -0
- package/dist/src/core/wire-dom-fragment.d.ts +23 -0
- package/dist/src/core/wire-dom-fragment.js +129 -0
- package/dist/src/core/wire-init.d.ts +23 -0
- package/dist/src/core/wire-init.js +201 -0
- package/dist/src/core/wire-subscript.d.ts +20 -0
- package/dist/src/core/wire-subscript.js +134 -0
- package/dist/src/core/wire-targets.d.ts +7 -0
- package/dist/src/core/wire-targets.js +9 -0
- package/dist/src/core/wire-utils.d.ts +88 -0
- package/dist/src/core/wire-utils.js +279 -0
- package/dist/src/errors/base.d.ts +60 -0
- package/dist/src/errors/base.js +87 -0
- package/dist/src/errors/build.d.ts +52 -0
- package/dist/src/errors/build.js +114 -0
- package/dist/src/errors/codes.d.ts +29 -0
- package/dist/src/errors/codes.js +30 -0
- package/dist/src/errors/config.d.ts +31 -0
- package/dist/src/errors/config.js +61 -0
- package/dist/src/errors/download.d.ts +42 -0
- package/dist/src/errors/download.js +95 -0
- package/dist/src/errors/furnace.d.ts +10 -0
- package/dist/src/errors/furnace.js +22 -0
- package/dist/src/errors/git.d.ts +41 -0
- package/dist/src/errors/git.js +99 -0
- package/dist/src/errors/patch.d.ts +10 -0
- package/dist/src/errors/patch.js +26 -0
- package/dist/src/errors/rebase.d.ts +20 -0
- package/dist/src/errors/rebase.js +30 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +21 -0
- package/dist/src/types/cli.d.ts +14 -0
- package/dist/src/types/cli.js +2 -0
- package/dist/src/types/commands/index.d.ts +6 -0
- package/dist/src/types/commands/index.js +6 -0
- package/dist/src/types/commands/options.d.ts +239 -0
- package/dist/src/types/commands/options.js +6 -0
- package/dist/src/types/commands/patches.d.ts +89 -0
- package/dist/src/types/commands/patches.js +6 -0
- package/dist/src/types/commands/project.d.ts +71 -0
- package/dist/src/types/commands/project.js +6 -0
- package/dist/src/types/config.d.ts +101 -0
- package/dist/src/types/config.js +2 -0
- package/dist/src/types/furnace.d.ts +158 -0
- package/dist/src/types/furnace.js +2 -0
- package/dist/src/types/index.d.ts +6 -0
- package/dist/src/types/index.js +6 -0
- package/dist/src/utils/errors.d.ts +2 -0
- package/dist/src/utils/errors.js +15 -0
- package/dist/src/utils/fs.d.ts +72 -0
- package/dist/src/utils/fs.js +179 -0
- package/dist/src/utils/logger.d.ts +58 -0
- package/dist/src/utils/logger.js +120 -0
- package/dist/src/utils/options.d.ts +8 -0
- package/dist/src/utils/options.js +16 -0
- package/dist/src/utils/package-root.d.ts +10 -0
- package/dist/src/utils/package-root.js +53 -0
- package/dist/src/utils/parse.d.ts +110 -0
- package/dist/src/utils/parse.js +200 -0
- package/dist/src/utils/paths.d.ts +10 -0
- package/dist/src/utils/paths.js +43 -0
- package/dist/src/utils/platform.d.ts +38 -0
- package/dist/src/utils/platform.js +56 -0
- package/dist/src/utils/process.d.ts +80 -0
- package/dist/src/utils/process.js +188 -0
- package/dist/src/utils/regex.d.ts +24 -0
- package/dist/src/utils/regex.js +40 -0
- package/dist/src/utils/validation.d.ts +133 -0
- package/dist/src/utils/validation.js +250 -0
- package/package.json +106 -0
- package/templates/configs/common.mozconfig +24 -0
- package/templates/configs/darwin.mozconfig +10 -0
- package/templates/configs/linux.mozconfig +12 -0
- package/templates/configs/win32.mozconfig +14 -0
- package/templates/licenses/0BSD.md +14 -0
- package/templates/licenses/EUPL-1.2.md +294 -0
- package/templates/licenses/GPL-2.0-or-later.md +339 -0
- package/templates/licenses/MPL-2.0.md +383 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Test manifest registration in browser/base/moz.build.
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { GeneralError } from '../errors/base.js';
|
|
7
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
8
|
+
import { findAlphabeticalMozBuildPosition, findAlphabeticalPosition } from './manifest-helpers.js';
|
|
9
|
+
import { tokenizeMozBuildList } from './manifest-tokenizers.js';
|
|
10
|
+
import { withParserFallback } from './parser-fallback.js';
|
|
11
|
+
/**
|
|
12
|
+
* Tokenizer-based implementation for test manifest registration.
|
|
13
|
+
*/
|
|
14
|
+
function registerTestManifestTokenized(content, testDir, entry) {
|
|
15
|
+
const lines = content.split('\n');
|
|
16
|
+
const listResult = tokenizeMozBuildList(lines, /BROWSER_CHROME_MANIFESTS/);
|
|
17
|
+
if (!listResult) {
|
|
18
|
+
throw new GeneralError('Could not find BROWSER_CHROME_MANIFESTS in browser/base/moz.build');
|
|
19
|
+
}
|
|
20
|
+
const { insertIndex, previousEntry } = findAlphabeticalMozBuildPosition(listResult.tokens, `content/test/${testDir}/browser.toml`);
|
|
21
|
+
lines.splice(insertIndex, 0, entry);
|
|
22
|
+
return { result: lines.join('\n'), previousEntry };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Legacy line-based implementation preserved as fallback.
|
|
26
|
+
*/
|
|
27
|
+
function legacyRegisterTestManifest(content, testDir, entry) {
|
|
28
|
+
const lines = content.split('\n');
|
|
29
|
+
const extractKey = (line) => {
|
|
30
|
+
const match = /"content\/test\/([^/]+)\/browser\.toml"/.exec(line);
|
|
31
|
+
return match?.[1];
|
|
32
|
+
};
|
|
33
|
+
let sectionStart = -1;
|
|
34
|
+
let sectionEnd = lines.length;
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
const line = lines[i];
|
|
37
|
+
if (line === undefined)
|
|
38
|
+
continue;
|
|
39
|
+
if (/^\s+"content\/test\/.*browser\.toml"/.test(line)) {
|
|
40
|
+
if (sectionStart === -1)
|
|
41
|
+
sectionStart = i;
|
|
42
|
+
sectionEnd = i + 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (sectionStart === -1) {
|
|
46
|
+
throw new GeneralError('Could not find test manifest section in browser/base/moz.build');
|
|
47
|
+
}
|
|
48
|
+
const { insertIndex, previousEntry } = findAlphabeticalPosition(lines, sectionStart, sectionEnd, testDir, extractKey);
|
|
49
|
+
lines.splice(insertIndex, 0, entry);
|
|
50
|
+
return { result: lines.join('\n'), previousEntry };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Registers a test manifest (browser.toml) in browser/base/moz.build.
|
|
54
|
+
*
|
|
55
|
+
* Entry format:
|
|
56
|
+
* "content/test/{dir}/browser.toml",
|
|
57
|
+
*/
|
|
58
|
+
export async function registerTestManifest(engineDir, testDir, dryRun = false) {
|
|
59
|
+
const manifest = 'browser/base/moz.build';
|
|
60
|
+
const manifestPath = join(engineDir, manifest);
|
|
61
|
+
if (!(await pathExists(manifestPath))) {
|
|
62
|
+
throw new GeneralError(`Manifest not found: ${manifest}`);
|
|
63
|
+
}
|
|
64
|
+
const entry = ` "content/test/${testDir}/browser.toml",`.replace(/\\/g, '/');
|
|
65
|
+
const content = await readText(manifestPath);
|
|
66
|
+
// Idempotency check
|
|
67
|
+
if (content.includes(`content/test/${testDir}/browser.toml`)) {
|
|
68
|
+
return { manifest, entry, skipped: true };
|
|
69
|
+
}
|
|
70
|
+
const { value } = withParserFallback(() => registerTestManifestTokenized(content, testDir, entry), () => legacyRegisterTestManifest(content, testDir, entry), manifest);
|
|
71
|
+
if (!dryRun) {
|
|
72
|
+
await writeText(manifestPath, value.result);
|
|
73
|
+
}
|
|
74
|
+
return { manifest, entry, previousEntry: value.previousEntry, skipped: false };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Deregisters a test manifest (browser.toml) from browser/base/moz.build.
|
|
78
|
+
* @param engineDir - Path to the engine directory
|
|
79
|
+
* @param testDir - Test directory name (e.g. 'mybrowser')
|
|
80
|
+
* @returns Whether the entry was removed
|
|
81
|
+
*/
|
|
82
|
+
export async function deregisterTestManifest(engineDir, testDir) {
|
|
83
|
+
const manifest = 'browser/base/moz.build';
|
|
84
|
+
const manifestPath = join(engineDir, manifest);
|
|
85
|
+
if (!(await pathExists(manifestPath))) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const content = await readText(manifestPath);
|
|
89
|
+
const entryPattern = `content/test/${testDir}/browser.toml`;
|
|
90
|
+
if (!content.includes(entryPattern)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
// Remove the line containing the entry (including trailing newline)
|
|
94
|
+
const lines = content.split('\n');
|
|
95
|
+
const filtered = lines.filter((line) => !line.includes(entryPattern));
|
|
96
|
+
await writeText(manifestPath, filtered.join('\n'));
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=register-test-manifest.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Runs an operation while holding the sidecar lock for a FireForge state file. */
|
|
2
|
+
export declare function withStateFileLock<T>(statePath: string, operation: () => Promise<T>): Promise<T>;
|
|
3
|
+
/** Renames a state file out of the way while preserving it for later inspection. */
|
|
4
|
+
export declare function quarantineStateFile(statePath: string, reason?: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { rename } from 'node:fs/promises';
|
|
3
|
+
import { basename } from 'node:path';
|
|
4
|
+
import { pathExists } from '../utils/fs.js';
|
|
5
|
+
import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
6
|
+
/** Runs an operation while holding the sidecar lock for a FireForge state file. */
|
|
7
|
+
export async function withStateFileLock(statePath, operation) {
|
|
8
|
+
return withFileLock(createSiblingLockPath(statePath, '.fireforge-state.lock'), operation, {
|
|
9
|
+
onTimeoutMessage: `Timed out waiting to update FireForge state at ${statePath}. ` +
|
|
10
|
+
'If no other fireforge process is running, remove the stale lock directory and retry.',
|
|
11
|
+
onStaleLockMessage: (ageMs) => `Removing stale FireForge state lock for ${basename(statePath)} ` +
|
|
12
|
+
`(age: ${Math.round(ageMs / 1000)}s). A previous fireforge process may have crashed.`,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/** Renames a state file out of the way while preserving it for later inspection. */
|
|
16
|
+
export async function quarantineStateFile(statePath, reason = 'corrupt') {
|
|
17
|
+
if (!(await pathExists(statePath))) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
21
|
+
const quarantinedPath = `${statePath}.${reason}-${timestamp}`;
|
|
22
|
+
await rename(statePath, quarantinedPath);
|
|
23
|
+
return basename(quarantinedPath);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=state-file.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TokenCoverageReport } from '../types/commands/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Measures design token coverage across CSS files.
|
|
4
|
+
*
|
|
5
|
+
* Counts var(--{prefix}*) usages, allowlisted vars, unknown vars, and raw
|
|
6
|
+
* color values. Reuses the same regex patterns as patch-lint.ts.
|
|
7
|
+
*
|
|
8
|
+
* @param repoDir - Absolute path to the engine (repository) directory
|
|
9
|
+
* @param cssFiles - File paths (relative to repoDir) to scan
|
|
10
|
+
* @returns Aggregate and per-file coverage report
|
|
11
|
+
*/
|
|
12
|
+
export declare function measureTokenCoverage(repoDir: string, cssFiles: string[], projectRoot?: string): Promise<TokenCoverageReport>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { toError } from '../utils/errors.js';
|
|
4
|
+
import { pathExists, readText } from '../utils/fs.js';
|
|
5
|
+
import { verbose } from '../utils/logger.js';
|
|
6
|
+
import { countRawCssColors } from '../utils/regex.js';
|
|
7
|
+
import { loadFurnaceConfig } from './furnace-config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Measures design token coverage across CSS files.
|
|
10
|
+
*
|
|
11
|
+
* Counts var(--{prefix}*) usages, allowlisted vars, unknown vars, and raw
|
|
12
|
+
* color values. Reuses the same regex patterns as patch-lint.ts.
|
|
13
|
+
*
|
|
14
|
+
* @param repoDir - Absolute path to the engine (repository) directory
|
|
15
|
+
* @param cssFiles - File paths (relative to repoDir) to scan
|
|
16
|
+
* @returns Aggregate and per-file coverage report
|
|
17
|
+
*/
|
|
18
|
+
export async function measureTokenCoverage(repoDir, cssFiles, projectRoot) {
|
|
19
|
+
// Load furnace config gracefully
|
|
20
|
+
let tokenPrefix;
|
|
21
|
+
let tokenAllowlist;
|
|
22
|
+
try {
|
|
23
|
+
const root = projectRoot ?? join(repoDir, '..');
|
|
24
|
+
const config = await loadFurnaceConfig(root);
|
|
25
|
+
if (config.tokenPrefix) {
|
|
26
|
+
tokenPrefix = config.tokenPrefix;
|
|
27
|
+
tokenAllowlist = new Set(config.tokenAllowlist ?? []);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
verbose(`Proceeding without furnace token metadata because furnace.json could not be loaded: ${toError(error).message}`);
|
|
32
|
+
}
|
|
33
|
+
const entries = [];
|
|
34
|
+
for (const file of cssFiles) {
|
|
35
|
+
const filePath = join(repoDir, file);
|
|
36
|
+
if (!(await pathExists(filePath)))
|
|
37
|
+
continue;
|
|
38
|
+
const rawCss = await readText(filePath);
|
|
39
|
+
// Strip block comments before scanning
|
|
40
|
+
const css = rawCss.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
41
|
+
// Count raw color values
|
|
42
|
+
const rawColors = countRawCssColors(css);
|
|
43
|
+
// Count custom property usages by category
|
|
44
|
+
let tokenUsages = 0;
|
|
45
|
+
let allowlisted = 0;
|
|
46
|
+
let unknownVars = 0;
|
|
47
|
+
const varPattern = /var\(\s*(--[\w-]+)/g;
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = varPattern.exec(css)) !== null) {
|
|
50
|
+
const prop = match[1];
|
|
51
|
+
if (!prop)
|
|
52
|
+
continue;
|
|
53
|
+
if (tokenPrefix && prop.startsWith(tokenPrefix)) {
|
|
54
|
+
tokenUsages++;
|
|
55
|
+
}
|
|
56
|
+
else if (tokenAllowlist?.has(prop)) {
|
|
57
|
+
allowlisted++;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
unknownVars++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
entries.push({ file, tokenUsages, allowlisted, unknownVars, rawColors });
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
filesScanned: entries.length,
|
|
67
|
+
tokenUsages: entries.reduce((s, e) => s + e.tokenUsages, 0),
|
|
68
|
+
allowlistedUsages: entries.reduce((s, e) => s + e.allowlisted, 0),
|
|
69
|
+
unknownVarUsages: entries.reduce((s, e) => s + e.unknownVars, 0),
|
|
70
|
+
rawColorCount: entries.reduce((s, e) => s + e.rawColors, 0),
|
|
71
|
+
files: entries,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=token-coverage.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dark mode behavior for a token.
|
|
3
|
+
*/
|
|
4
|
+
export type TokenMode = 'auto' | 'static' | 'override';
|
|
5
|
+
/**
|
|
6
|
+
* Options for adding a token.
|
|
7
|
+
*/
|
|
8
|
+
export interface AddTokenOptions {
|
|
9
|
+
/** Full token name including prefix (e.g., "--mybrowser-widget-dot-size") */
|
|
10
|
+
tokenName: string;
|
|
11
|
+
/** CSS value (e.g., "1px", "var(--space-small)", "light-dark(#fff, #000)") */
|
|
12
|
+
value: string;
|
|
13
|
+
/** Token category matching section headers in the CSS file */
|
|
14
|
+
category: string;
|
|
15
|
+
/** Dark mode behavior */
|
|
16
|
+
mode: TokenMode;
|
|
17
|
+
/** Comment description for the CSS file */
|
|
18
|
+
description?: string | undefined;
|
|
19
|
+
/** Dark mode value (required if mode is "override") */
|
|
20
|
+
darkValue?: string | undefined;
|
|
21
|
+
/** Dry run mode */
|
|
22
|
+
dryRun?: boolean | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Result of adding a token.
|
|
26
|
+
*/
|
|
27
|
+
export interface AddTokenResult {
|
|
28
|
+
/** Whether the token was added to CSS */
|
|
29
|
+
cssAdded: boolean;
|
|
30
|
+
/** Whether the token was added to the docs table */
|
|
31
|
+
docsAdded: boolean;
|
|
32
|
+
/** Whether it was added to the unmapped table */
|
|
33
|
+
unmappedAdded: boolean;
|
|
34
|
+
/** Whether the count table was updated */
|
|
35
|
+
countUpdated: boolean;
|
|
36
|
+
/** Whether the operation was skipped (already exists) */
|
|
37
|
+
skipped: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Returns the token CSS path relative to engine root for a given binary name. */
|
|
40
|
+
export declare function getTokensCssPath(binaryName: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Validates token-add inputs without mutating files.
|
|
43
|
+
*
|
|
44
|
+
* @param root - Project root directory
|
|
45
|
+
* @param options - Token options
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateTokenAdd(root: string, options: AddTokenOptions): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Adds a design token to the CSS file and documentation.
|
|
50
|
+
*
|
|
51
|
+
* @param root - Project root directory
|
|
52
|
+
* @param options - Token options
|
|
53
|
+
* @returns Result of the operation
|
|
54
|
+
*/
|
|
55
|
+
export declare function addToken(root: string, options: AddTokenOptions): Promise<AddTokenResult>;
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { GeneralError, InvalidArgumentError } from '../errors/base.js';
|
|
4
|
+
import { FurnaceError } from '../errors/furnace.js';
|
|
5
|
+
import { toError } from '../utils/errors.js';
|
|
6
|
+
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
7
|
+
import { warn } from '../utils/logger.js';
|
|
8
|
+
import { escapeRegex } from '../utils/regex.js';
|
|
9
|
+
import { validateTokenName } from '../utils/validation.js';
|
|
10
|
+
import { getProjectPaths, loadConfig } from './config.js';
|
|
11
|
+
import { loadFurnaceConfig } from './furnace-config.js';
|
|
12
|
+
/** Returns the token CSS path relative to engine root for a given binary name. */
|
|
13
|
+
export function getTokensCssPath(binaryName) {
|
|
14
|
+
return `browser/themes/shared/${binaryName}-tokens.css`;
|
|
15
|
+
}
|
|
16
|
+
const TOKENS_DOC = 'docs/design/SRC_TOKENS.md';
|
|
17
|
+
/**
|
|
18
|
+
* Determines the mode annotation string for the CSS comment.
|
|
19
|
+
*/
|
|
20
|
+
function getModeAnnotation(mode, value) {
|
|
21
|
+
if (mode === 'override')
|
|
22
|
+
return 'override';
|
|
23
|
+
if (mode === 'auto') {
|
|
24
|
+
if (value.includes('light-dark('))
|
|
25
|
+
return 'auto (light-dark)';
|
|
26
|
+
return 'auto';
|
|
27
|
+
}
|
|
28
|
+
// static
|
|
29
|
+
if (value.startsWith('var(--'))
|
|
30
|
+
return 'static';
|
|
31
|
+
return 'static, fork-specific';
|
|
32
|
+
}
|
|
33
|
+
async function resolveTokenAddContext(root) {
|
|
34
|
+
const { engine: engineDir } = getProjectPaths(root);
|
|
35
|
+
const forgeConfig = await loadConfig(root);
|
|
36
|
+
return {
|
|
37
|
+
engineDir,
|
|
38
|
+
tokensCssPath: getTokensCssPath(forgeConfig.binaryName),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function validateTokenPrefix(root, options) {
|
|
42
|
+
try {
|
|
43
|
+
const config = await loadFurnaceConfig(root);
|
|
44
|
+
if (config.tokenPrefix && !options.tokenName.startsWith(config.tokenPrefix)) {
|
|
45
|
+
throw new InvalidArgumentError(`Token name "${options.tokenName}" does not match the configured prefix "${config.tokenPrefix}".`, 'tokenName');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof InvalidArgumentError)
|
|
50
|
+
throw error;
|
|
51
|
+
// FurnaceError means furnace.json doesn't exist yet — skip silently.
|
|
52
|
+
// Other errors (parse errors, permission errors) deserve a warning.
|
|
53
|
+
if (!(error instanceof FurnaceError)) {
|
|
54
|
+
const message = toError(error).message;
|
|
55
|
+
warn(`Skipping token prefix validation: ${message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function validateTokenNameSyntax(tokenName) {
|
|
60
|
+
const error = validateTokenName(tokenName);
|
|
61
|
+
if (error) {
|
|
62
|
+
throw new InvalidArgumentError(error, 'tokenName');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function validateDarkValue(options) {
|
|
66
|
+
if (options.mode === 'override' && !options.darkValue) {
|
|
67
|
+
throw new InvalidArgumentError('Override mode requires --dark-value to be specified.', 'darkValue');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function assertTokenCategoryExists(engineDir, tokensCssPath, category) {
|
|
71
|
+
const filePath = join(engineDir, tokensCssPath);
|
|
72
|
+
if (!(await pathExists(filePath))) {
|
|
73
|
+
throw new GeneralError(`Token CSS file not found: ${tokensCssPath}`);
|
|
74
|
+
}
|
|
75
|
+
const content = await readText(filePath);
|
|
76
|
+
const lines = content.split('\n');
|
|
77
|
+
const escapedCategory = escapeRegex(category);
|
|
78
|
+
const singleLinePattern = new RegExp(`\\/\\*\\s*=.*${escapedCategory}.*=\\s*\\*\\/`);
|
|
79
|
+
for (let i = 0; i < lines.length; i++) {
|
|
80
|
+
const line = lines[i] ?? '';
|
|
81
|
+
if (singleLinePattern.test(line)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (/^\s*\/\*\s*=+/.test(line) && !/\*\//.test(line)) {
|
|
85
|
+
for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
|
|
86
|
+
const blockLine = lines[j] ?? '';
|
|
87
|
+
if (new RegExp(escapedCategory).test(blockLine)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (/\*\//.test(blockLine))
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw new GeneralError(`Category "${category}" not found in ${tokensCssPath}. ` +
|
|
96
|
+
'Available categories are defined by /* =... category ...= */ comment headers.');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validates token-add inputs without mutating files.
|
|
100
|
+
*
|
|
101
|
+
* @param root - Project root directory
|
|
102
|
+
* @param options - Token options
|
|
103
|
+
*/
|
|
104
|
+
export async function validateTokenAdd(root, options) {
|
|
105
|
+
const { engineDir, tokensCssPath } = await resolveTokenAddContext(root);
|
|
106
|
+
validateTokenNameSyntax(options.tokenName);
|
|
107
|
+
await validateTokenPrefix(root, options);
|
|
108
|
+
validateDarkValue(options);
|
|
109
|
+
await assertTokenCategoryExists(engineDir, tokensCssPath, options.category);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Adds a design token to the CSS file and documentation.
|
|
113
|
+
*
|
|
114
|
+
* @param root - Project root directory
|
|
115
|
+
* @param options - Token options
|
|
116
|
+
* @returns Result of the operation
|
|
117
|
+
*/
|
|
118
|
+
export async function addToken(root, options) {
|
|
119
|
+
const { engineDir, tokensCssPath } = await resolveTokenAddContext(root);
|
|
120
|
+
validateTokenNameSyntax(options.tokenName);
|
|
121
|
+
await validateTokenPrefix(root, options);
|
|
122
|
+
validateDarkValue(options);
|
|
123
|
+
if (options.dryRun) {
|
|
124
|
+
await validateTokenAdd(root, options);
|
|
125
|
+
const filePath = join(engineDir, tokensCssPath);
|
|
126
|
+
const content = await readText(filePath);
|
|
127
|
+
const stripped = content.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
128
|
+
const skipped = stripped.includes(options.tokenName + ':');
|
|
129
|
+
return {
|
|
130
|
+
cssAdded: !skipped,
|
|
131
|
+
docsAdded: !skipped,
|
|
132
|
+
unmappedAdded: !skipped && !options.value.startsWith('var('),
|
|
133
|
+
countUpdated: !skipped,
|
|
134
|
+
skipped,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
// --- CSS file ---
|
|
138
|
+
const cssAdded = await addTokenToCSS(engineDir, options, tokensCssPath);
|
|
139
|
+
if (!cssAdded) {
|
|
140
|
+
return {
|
|
141
|
+
cssAdded: false,
|
|
142
|
+
docsAdded: false,
|
|
143
|
+
unmappedAdded: false,
|
|
144
|
+
countUpdated: false,
|
|
145
|
+
skipped: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// --- Documentation ---
|
|
149
|
+
const docsResult = await addTokenToDocs(engineDir, options);
|
|
150
|
+
return {
|
|
151
|
+
cssAdded,
|
|
152
|
+
docsAdded: docsResult.docsAdded,
|
|
153
|
+
unmappedAdded: docsResult.unmappedAdded,
|
|
154
|
+
countUpdated: docsResult.countUpdated,
|
|
155
|
+
skipped: false,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function findCategorySection(lines, category, tokensCssPath) {
|
|
159
|
+
const escapedCategory = escapeRegex(category);
|
|
160
|
+
const singleLinePattern = new RegExp(`\\/\\*\\s*=.*${escapedCategory}.*=\\s*\\*\\/`);
|
|
161
|
+
let categoryLine = -1;
|
|
162
|
+
for (let i = 0; i < lines.length; i++) {
|
|
163
|
+
const line = lines[i] ?? '';
|
|
164
|
+
// Check single-line format: /* = Category = */
|
|
165
|
+
if (singleLinePattern.test(line)) {
|
|
166
|
+
categoryLine = i;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
// Check multi-line format: line opens a block comment with === but does NOT close it
|
|
170
|
+
// e.g., "/* ================================================================"
|
|
171
|
+
// (NOT "/* ================================================= */" which closes on the same line)
|
|
172
|
+
if (/^\s*\/\*\s*=+/.test(line) && !/\*\//.test(line)) {
|
|
173
|
+
// Look ahead within the comment block (up to 5 lines) for the category text
|
|
174
|
+
for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
|
|
175
|
+
const blockLine = lines[j] ?? '';
|
|
176
|
+
if (new RegExp(escapedCategory).test(blockLine)) {
|
|
177
|
+
categoryLine = i;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
// Stop if we've exited the comment block
|
|
181
|
+
if (/\*\//.test(blockLine))
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
if (categoryLine !== -1)
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (categoryLine === -1) {
|
|
189
|
+
throw new GeneralError(`Category "${category}" not found in ${tokensCssPath}. ` +
|
|
190
|
+
'Available categories are defined by /* =... category ...= */ comment headers.');
|
|
191
|
+
}
|
|
192
|
+
// Find the end of this category section (next section header or closing })
|
|
193
|
+
// Handles both single-line (/* =...= */) and multi-line (/* ===...) section delimiters
|
|
194
|
+
// Skip past the current header block first
|
|
195
|
+
let scanStart = categoryLine + 1;
|
|
196
|
+
for (let i = categoryLine + 1; i < lines.length; i++) {
|
|
197
|
+
const line = lines[i] ?? '';
|
|
198
|
+
// Skip lines that are part of the current header comment block
|
|
199
|
+
if (/^\s*\/\*\s*=/.test(line) || /^\s*\*\s*=/.test(line) || /^\s*\*\//.test(line)) {
|
|
200
|
+
scanStart = i + 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
let sectionEnd = lines.length;
|
|
206
|
+
for (let i = scanStart; i < lines.length; i++) {
|
|
207
|
+
const line = lines[i] ?? '';
|
|
208
|
+
if (/\/\*\s*=.*=\s*\*\//.test(line) ||
|
|
209
|
+
(/^\s*\/\*\s*=+/.test(line) && !/\*\//.test(line)) ||
|
|
210
|
+
/^\s*\}/.test(line)) {
|
|
211
|
+
sectionEnd = i;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { categoryLine, sectionEnd };
|
|
216
|
+
}
|
|
217
|
+
function insertDarkModeOverride(lines, options) {
|
|
218
|
+
if (options.mode !== 'override' || !options.darkValue)
|
|
219
|
+
return;
|
|
220
|
+
let darkMediaLine = -1;
|
|
221
|
+
for (let i = 0; i < lines.length; i++) {
|
|
222
|
+
if (/prefers-color-scheme:\s*dark/.test(lines[i] ?? '')) {
|
|
223
|
+
darkMediaLine = i;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (darkMediaLine === -1)
|
|
228
|
+
return;
|
|
229
|
+
// Find the closing } of the @media block
|
|
230
|
+
let darkBlockEnd = lines.length;
|
|
231
|
+
let depth = 0;
|
|
232
|
+
let entryDepth = 0;
|
|
233
|
+
let enteredBlock = false;
|
|
234
|
+
for (let i = darkMediaLine; i < lines.length; i++) {
|
|
235
|
+
const line = lines[i] ?? '';
|
|
236
|
+
for (const ch of line) {
|
|
237
|
+
if (ch === '{') {
|
|
238
|
+
depth++;
|
|
239
|
+
if (!enteredBlock) {
|
|
240
|
+
entryDepth = depth - 1;
|
|
241
|
+
enteredBlock = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (ch === '}')
|
|
245
|
+
depth--;
|
|
246
|
+
}
|
|
247
|
+
if (enteredBlock && depth === entryDepth) {
|
|
248
|
+
darkBlockEnd = i;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Insert the dark value before the closing }
|
|
253
|
+
const darkEntry = ` ${options.tokenName}: ${options.darkValue};`;
|
|
254
|
+
lines.splice(darkBlockEnd, 0, darkEntry);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Adds a token declaration to the CSS file in the correct category section.
|
|
258
|
+
*/
|
|
259
|
+
async function addTokenToCSS(engineDir, options, tokensCssPath) {
|
|
260
|
+
const filePath = join(engineDir, tokensCssPath);
|
|
261
|
+
await assertTokenCategoryExists(engineDir, tokensCssPath, options.category);
|
|
262
|
+
let content = await readText(filePath);
|
|
263
|
+
// Idempotency check — strip CSS block comments so we don't match inside them
|
|
264
|
+
const stripped = content.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
265
|
+
if (stripped.includes(options.tokenName + ':')) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
const lines = content.split('\n');
|
|
269
|
+
const annotation = getModeAnnotation(options.mode, options.value);
|
|
270
|
+
const { categoryLine, sectionEnd } = findCategorySection(lines, options.category, tokensCssPath);
|
|
271
|
+
// Build the insertion lines
|
|
272
|
+
const insertLines = [];
|
|
273
|
+
if (options.description) {
|
|
274
|
+
insertLines.push(` /* ${options.description} */`);
|
|
275
|
+
}
|
|
276
|
+
insertLines.push(` ${options.tokenName}: ${options.value}; /* ${annotation} */`);
|
|
277
|
+
// Insert before the section end (before next header or closing brace)
|
|
278
|
+
// Find last non-blank line in the section to insert after it
|
|
279
|
+
let insertIndex = sectionEnd;
|
|
280
|
+
for (let i = sectionEnd - 1; i > categoryLine; i--) {
|
|
281
|
+
if ((lines[i] ?? '').trim()) {
|
|
282
|
+
insertIndex = i + 1;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
lines.splice(insertIndex, 0, ...insertLines);
|
|
287
|
+
insertDarkModeOverride(lines, options);
|
|
288
|
+
content = lines.join('\n');
|
|
289
|
+
await writeText(filePath, content);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Adds a token to the documentation markdown file.
|
|
294
|
+
*/
|
|
295
|
+
async function addTokenToDocs(engineDir, options) {
|
|
296
|
+
const filePath = join(engineDir, '..', TOKENS_DOC);
|
|
297
|
+
if (!(await pathExists(filePath))) {
|
|
298
|
+
// Docs file is optional
|
|
299
|
+
return { docsAdded: false, unmappedAdded: false, countUpdated: false };
|
|
300
|
+
}
|
|
301
|
+
let content = await readText(filePath);
|
|
302
|
+
const lines = content.split('\n');
|
|
303
|
+
let docsAdded = false;
|
|
304
|
+
let unmappedAdded = false;
|
|
305
|
+
let countUpdated = false;
|
|
306
|
+
const annotation = getModeAnnotation(options.mode, options.value);
|
|
307
|
+
const isLiteral = !options.value.startsWith('var(');
|
|
308
|
+
// Find the category group in the token table
|
|
309
|
+
// Look for a row containing the category name, then find the last row in that group
|
|
310
|
+
let categoryRowStart = -1;
|
|
311
|
+
let lastRowInCategory = -1;
|
|
312
|
+
for (let i = 0; i < lines.length; i++) {
|
|
313
|
+
const line = lines[i] ?? '';
|
|
314
|
+
// Table rows start with |
|
|
315
|
+
if (line.startsWith('|') && line.includes(options.category)) {
|
|
316
|
+
categoryRowStart = i;
|
|
317
|
+
lastRowInCategory = i;
|
|
318
|
+
}
|
|
319
|
+
else if (categoryRowStart !== -1 &&
|
|
320
|
+
line.startsWith('|') &&
|
|
321
|
+
!line.startsWith('|--') &&
|
|
322
|
+
!line.startsWith('| Token')) {
|
|
323
|
+
// Check if this row still belongs to the same category (no category cell or empty category)
|
|
324
|
+
const cells = line.split('|').map((c) => c.trim());
|
|
325
|
+
// If the first data cell (after leading empty) is empty, it belongs to same category
|
|
326
|
+
if (cells[1] === '' || !cells[1]) {
|
|
327
|
+
lastRowInCategory = i;
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else if (categoryRowStart !== -1 && !line.startsWith('|')) {
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (lastRowInCategory !== -1) {
|
|
338
|
+
// Insert a new row after the last row in this category
|
|
339
|
+
const mapsTo = isLiteral ? '—' : options.value.replace(/var\(([^)]+)\)/, '$1');
|
|
340
|
+
const newRow = `| | \`${options.tokenName}\` | \`${options.value}\` | ${mapsTo} | ${annotation} |`;
|
|
341
|
+
lines.splice(lastRowInCategory + 1, 0, newRow);
|
|
342
|
+
docsAdded = true;
|
|
343
|
+
}
|
|
344
|
+
// If the value is a literal (not a var() reference), add to unmapped table
|
|
345
|
+
if (isLiteral) {
|
|
346
|
+
let unmappedTableStart = -1;
|
|
347
|
+
for (let i = 0; i < lines.length; i++) {
|
|
348
|
+
const line = lines[i] ?? '';
|
|
349
|
+
if (/not yet mapped/i.test(line) || /unmapped/i.test(line)) {
|
|
350
|
+
unmappedTableStart = i;
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (unmappedTableStart !== -1) {
|
|
355
|
+
// Find the last row of the unmapped table
|
|
356
|
+
let lastUnmappedRow = unmappedTableStart;
|
|
357
|
+
for (let i = unmappedTableStart + 1; i < lines.length; i++) {
|
|
358
|
+
const line = lines[i] ?? '';
|
|
359
|
+
if (line.startsWith('|') && !line.startsWith('|--') && !line.startsWith('| Token')) {
|
|
360
|
+
lastUnmappedRow = i;
|
|
361
|
+
}
|
|
362
|
+
else if (!line.startsWith('|')) {
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const unmappedRow = `| \`${options.tokenName}\` | \`${options.value}\` | ${options.description ?? ''} |`;
|
|
367
|
+
lines.splice(lastUnmappedRow + 1, 0, unmappedRow);
|
|
368
|
+
unmappedAdded = true;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Update dark/light mode behavior count table
|
|
372
|
+
const modeCountPattern = new RegExp(`\\|\\s*${options.mode}\\s*\\|\\s*(\\d+)\\s*\\|`);
|
|
373
|
+
for (let i = 0; i < lines.length; i++) {
|
|
374
|
+
const line = lines[i] ?? '';
|
|
375
|
+
const match = modeCountPattern.exec(line);
|
|
376
|
+
if (match) {
|
|
377
|
+
const oldCount = parseInt(match[1] ?? '0', 10);
|
|
378
|
+
lines[i] = line.replace(modeCountPattern, `| ${options.mode} | ${oldCount + 1} |`);
|
|
379
|
+
countUpdated = true;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
content = lines.join('\n');
|
|
384
|
+
await writeText(filePath, content);
|
|
385
|
+
return { docsAdded, unmappedAdded, countUpdated };
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=token-manager.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-init.js — destroy expression in onUnload()/uninit().
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* AST-based implementation: finds onUnload()/uninit() method body and
|
|
6
|
+
* inserts the destroy block at the top (LIFO ordering).
|
|
7
|
+
*/
|
|
8
|
+
export declare function addDestroyAST(content: string, expression: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Legacy regex/line-based implementation preserved as fallback.
|
|
11
|
+
*/
|
|
12
|
+
export declare function legacyAddDestroy(content: string, expression: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Adds a destroy expression to the top of onUnload() or uninit() in
|
|
15
|
+
* browser-init.js (LIFO ordering — newest first).
|
|
16
|
+
*
|
|
17
|
+
* @param engineDir - Engine source root
|
|
18
|
+
* @param expression - The destroy expression (e.g., "MyComponent.destroy()")
|
|
19
|
+
* @returns true if added, false if already present
|
|
20
|
+
*/
|
|
21
|
+
export declare function addDestroyToBrowserInit(engineDir: string, expression: string): Promise<boolean>;
|