domflax 0.1.1 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/dist/{chunk-DNHOGPYV.js → chunk-3Z5ZWLXX.js} +407 -51
- package/dist/chunk-3Z5ZWLXX.js.map +1 -0
- package/dist/{chunk-DOQEBGWB.js → chunk-5FWENSD2.js} +63 -8
- package/dist/chunk-5FWENSD2.js.map +1 -0
- package/dist/chunk-EVENAJYI.js +336 -0
- package/dist/chunk-EVENAJYI.js.map +1 -0
- package/dist/{chunk-DWLB7FRR.js → chunk-H5KTGI3A.js} +153 -7
- package/dist/chunk-H5KTGI3A.js.map +1 -0
- package/dist/{chunk-6WVVF6AD.js → chunk-U5GOONKV.js} +5 -2
- package/dist/{chunk-6WVVF6AD.js.map → chunk-U5GOONKV.js.map} +1 -1
- package/dist/cli.cjs +1033 -178
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +285 -243
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +614 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -18
- package/dist/index.d.ts +34 -18
- package/dist/index.js +4 -4
- package/dist/{pattern-F5xBtIE-.d.cts → pattern-CP9_HpVK.d.cts} +1 -1
- package/dist/{pattern-CV607P87.d.ts → pattern-CYgsv-jO.d.ts} +1 -1
- package/dist/pattern-kit.cjs.map +1 -1
- package/dist/pattern-kit.d.cts +2 -2
- package/dist/pattern-kit.d.ts +2 -2
- package/dist/pattern-kit.js +2 -2
- package/dist/{resolve-ops-DIwEelH-.d.ts → resolve-ops-Ci7LgYHC.d.cts} +9 -0
- package/dist/{resolve-ops-DIwEelH-.d.cts → resolve-ops-Ci7LgYHC.d.ts} +9 -0
- package/dist/verify.d.cts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +1 -1
- package/dist/webpack-loader.cjs +614 -68
- package/dist/webpack-loader.cjs.map +1 -1
- package/dist/webpack-loader.d.cts +2 -2
- package/dist/webpack-loader.d.ts +2 -2
- package/dist/webpack-loader.js +4 -4
- package/dist/worker.cjs +5955 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.cts +2 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.js +72 -0
- package/dist/worker.js.map +1 -0
- package/package.json +4 -2
- package/dist/chunk-DNHOGPYV.js.map +0 -1
- package/dist/chunk-DOQEBGWB.js.map +0 -1
- package/dist/chunk-DWLB7FRR.js.map +0 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
builtinPatterns,
|
|
3
3
|
createCssResolver,
|
|
4
|
+
createHtmlBackend,
|
|
5
|
+
createHtmlFrontend,
|
|
4
6
|
createJsxBackend,
|
|
5
7
|
createJsxFrontend,
|
|
6
8
|
createTailwindResolver
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-3Z5ZWLXX.js";
|
|
8
10
|
import {
|
|
9
11
|
buildSelectorIndex,
|
|
10
12
|
createPipeline,
|
|
@@ -12,10 +14,10 @@ import {
|
|
|
12
14
|
normalizer,
|
|
13
15
|
runPasses,
|
|
14
16
|
syncClassesFromComputed
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-H5KTGI3A.js";
|
|
16
18
|
import {
|
|
17
19
|
init_esm_shims
|
|
18
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-U5GOONKV.js";
|
|
19
21
|
|
|
20
22
|
// src/index.ts
|
|
21
23
|
init_esm_shims();
|
|
@@ -30,6 +32,11 @@ function jsxKindOf(id) {
|
|
|
30
32
|
if (clean.endsWith(".jsx")) return "jsx";
|
|
31
33
|
return null;
|
|
32
34
|
}
|
|
35
|
+
function htmlKindOf(id) {
|
|
36
|
+
const clean = (id.split("?", 1)[0] ?? id).toLowerCase();
|
|
37
|
+
if (clean.endsWith(".html") || clean.endsWith(".htm")) return "html";
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
33
40
|
function eolOf(doc) {
|
|
34
41
|
for (const src of doc.sources.values()) return src.eol;
|
|
35
42
|
return "\n";
|
|
@@ -97,9 +104,51 @@ function runJsxPipeline(code, id, kind, resolver, patterns, safety) {
|
|
|
97
104
|
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
98
105
|
return finishPipeline(optimized, id, resolver);
|
|
99
106
|
}
|
|
107
|
+
function prepareHtml(code, id, resolver, patterns, safety, gate) {
|
|
108
|
+
const parsed = createHtmlFrontend().parse(code, {
|
|
109
|
+
id,
|
|
110
|
+
kind: "html",
|
|
111
|
+
resolver,
|
|
112
|
+
normalizer,
|
|
113
|
+
config: {},
|
|
114
|
+
onDiagnostic: () => {
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
const doc = parsed.doc;
|
|
118
|
+
const ctx = {
|
|
119
|
+
doc,
|
|
120
|
+
safetyCeiling: safety,
|
|
121
|
+
normalizer,
|
|
122
|
+
selectors: buildSelectorIndex(doc, resolver),
|
|
123
|
+
resolver,
|
|
124
|
+
gate
|
|
125
|
+
};
|
|
126
|
+
return { doc, ctx, passes: buildPasses(patterns) };
|
|
127
|
+
}
|
|
128
|
+
function finishHtmlPipeline(optimized, id, resolver) {
|
|
129
|
+
syncClassesFromComputed(optimized, resolver, normalizer);
|
|
130
|
+
const printed = createHtmlBackend().print(
|
|
131
|
+
optimized,
|
|
132
|
+
{ moduleId: id, ops: [], provenance: /* @__PURE__ */ new Map() },
|
|
133
|
+
{
|
|
134
|
+
normalizer,
|
|
135
|
+
resolver,
|
|
136
|
+
sink: createSyntheticSink(),
|
|
137
|
+
eol: eolOf(optimized),
|
|
138
|
+
onDiagnostic: () => {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
return printed.code;
|
|
143
|
+
}
|
|
144
|
+
function runHtmlPipeline(code, id, resolver, patterns, safety) {
|
|
145
|
+
const { doc, ctx, passes } = prepareHtml(code, id, resolver, patterns, safety, "provably-safe");
|
|
146
|
+
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
147
|
+
return finishHtmlPipeline(optimized, id, resolver);
|
|
148
|
+
}
|
|
100
149
|
|
|
101
150
|
// src/index.ts
|
|
102
|
-
var DEFAULT_INCLUDE = [".jsx", ".tsx", ".html"];
|
|
151
|
+
var DEFAULT_INCLUDE = [".jsx", ".tsx", ".html", ".htm"];
|
|
103
152
|
function resolveOptions(options) {
|
|
104
153
|
return {
|
|
105
154
|
provider: options.provider ?? "auto",
|
|
@@ -135,9 +184,15 @@ function createDomflax(options = {}) {
|
|
|
135
184
|
transform(code, id) {
|
|
136
185
|
if (!isSupported(id, resolved.include)) return { code, map: null };
|
|
137
186
|
const kind = jsxKindOf(id);
|
|
138
|
-
if (kind
|
|
139
|
-
|
|
140
|
-
|
|
187
|
+
if (kind !== null) {
|
|
188
|
+
const out = runJsxPipeline(code, id, kind, getResolver(), patterns, resolved.safety);
|
|
189
|
+
return { code: out, map: null };
|
|
190
|
+
}
|
|
191
|
+
if (htmlKindOf(id) !== null) {
|
|
192
|
+
const out = runHtmlPipeline(code, id, getResolver(), patterns, resolved.safety);
|
|
193
|
+
return { code: out, map: null };
|
|
194
|
+
}
|
|
195
|
+
return { code, map: null };
|
|
141
196
|
}
|
|
142
197
|
};
|
|
143
198
|
}
|
|
@@ -185,4 +240,4 @@ export {
|
|
|
185
240
|
webpack,
|
|
186
241
|
src_default
|
|
187
242
|
};
|
|
188
|
-
//# sourceMappingURL=chunk-
|
|
243
|
+
//# sourceMappingURL=chunk-5FWENSD2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/pipeline-run.ts"],"sourcesContent":["/**\n * domflax — public meta package.\n *\n * Re-exports the entire `@domflax/core` public API (types + reference runtime) and the built-in\n * `@domflax/patterns` library, then layers thin, framework-agnostic build adapters on top\n * (`vite()` / `webpack()`) plus a programmatic `createDomflax()` factory.\n *\n * Each adapter runs the SAME single-file engine as {@link createDomflax} (JSX/TSX + HTML frontends +\n * lazy Tailwind/CSS resolver → core pass manager → reverse-emit → surgical backend). The adapters are\n * structurally typed against their bundlers — they never hard-depend on `vite` or `webpack`.\n *\n * `.jsx`/`.tsx` route to `@domflax/frontend-jsx` (Babel); `.html`/`.htm` route to\n * `@domflax/frontend-html` (parse5). Both emit via SURGICAL span edits over the original source.\n */\n\nimport { createPipeline } from '@domflax/core';\nimport type {\n EncodedSourceMap,\n Pattern,\n Pipeline,\n SafetyLevel,\n StyleResolver,\n} from '@domflax/core';\nimport { builtinPatterns } from '@domflax/patterns';\nimport { createTailwindResolver } from '@domflax/resolver-tailwind';\nimport { createCssResolver } from '@domflax/resolver-css';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { htmlKindOf, jsxKindOf, runHtmlPipeline, runJsxPipeline } from './pipeline-run';\n\n// ── Re-export the public surface ──────────────────────────────────────────────────────────────\nexport * from '@domflax/core';\nexport * from '@domflax/patterns';\n\n/* ────────────────────────────────────────────────────────────────────────── *\n * Options\n * ────────────────────────────────────────────────────────────────────────── */\n\n/** How class names resolve to computed styles. */\nexport type DomflaxProvider = 'auto' | 'tailwind' | 'custom';\n\n/** Public adapter/factory options (mirrors the documented `domflax({...})` surface). */\nexport interface DomflaxOptions {\n /** Resolution strategy. Defaults to `'auto'`. */\n readonly provider?: DomflaxProvider;\n /** Stylesheets to parse when `provider` is `'custom'`. */\n readonly cssFiles?: readonly string[];\n /** Preview changes without rewriting source. */\n readonly dryRun?: boolean;\n /** Optimization aggressiveness handed to the pass manager (0 lint … 3 aggressive). */\n readonly safety?: SafetyLevel;\n /** File globs/extensions the adapters should consider. Defaults to jsx/tsx/html. */\n readonly include?: readonly string[];\n}\n\n/** Fully-resolved options with defaults applied. */\nexport interface ResolvedDomflaxOptions {\n readonly provider: DomflaxProvider;\n readonly cssFiles: readonly string[];\n readonly dryRun: boolean;\n readonly safety: SafetyLevel;\n readonly include: readonly string[];\n}\n\nconst DEFAULT_INCLUDE: readonly string[] = ['.jsx', '.tsx', '.html', '.htm'];\n\nfunction resolveOptions(options: DomflaxOptions): ResolvedDomflaxOptions {\n return {\n provider: options.provider ?? 'auto',\n cssFiles: options.cssFiles ?? [],\n dryRun: options.dryRun ?? false,\n safety: options.safety ?? 2,\n include: options.include ?? DEFAULT_INCLUDE,\n };\n}\n\n/** True when `id` is a file domflax knows how to transform. */\nfunction isSupported(id: string, include: readonly string[]): boolean {\n // Strip query suffixes bundlers append (e.g. `App.tsx?used`).\n const clean = id.split('?', 1)[0] ?? id;\n return include.some((ext) => clean.endsWith(ext));\n}\n\n/* ────────────────────────────────────────────────────────────────────────── *\n * Programmatic instance\n * ────────────────────────────────────────────────────────────────────────── */\n\n/** Result of a single-file transform. `map` is null until codegen lands. */\nexport interface DomflaxTransformResult {\n readonly code: string;\n readonly map: EncodedSourceMap | null;\n}\n\n/**\n * A configured domflax engine. Holds the wired core {@link Pipeline}, the passthrough\n * {@link StyleResolver}, and the built-in {@link Pattern} set, and exposes a single-file\n * `transform`.\n */\nexport interface Domflax {\n readonly options: ResolvedDomflaxOptions;\n readonly pipeline: Pipeline;\n readonly resolver: StyleResolver;\n readonly patterns: readonly Pattern[];\n /**\n * Transform one file (SYNCHRONOUS, fully static, never launches a browser). For `.jsx`/`.tsx` this\n * runs the full pipeline (parse → resolve → flatten[provably-safe only] → reverse-emit → print);\n * every other (or unsupported) file is returned unchanged. Only provably layout-neutral flattens are\n * applied — domflax never changes rendering.\n */\n transform(code: string, id: string): DomflaxTransformResult;\n}\n\n/**\n * Build a configured domflax engine.\n *\n * Wires a real single-file pipeline: the JSX/TSX frontend + a Tailwind resolver feed the core pass\n * manager (running {@link builtinPatterns}), whose output is reverse-emitted back to class tokens\n * and re-printed by the JSX backend. Non-jsx/tsx files pass through unchanged.\n */\n/**\n * Build the {@link StyleResolver} for the chosen provider. The heavy engine each resolver wraps\n * (Tailwind v3 / postcss) is loaded LAZILY — at the moment this factory runs — and resolved from the\n * CONSUMER'S project, NOT from domflax's (possibly bundled) location. Both engines are OPTIONAL peer\n * dependencies of the published `domflax`: a Tailwind-only user never triggers a postcss load, and a\n * custom-CSS-only user never triggers a Tailwind load, because only the selected branch constructs.\n */\nfunction createResolver(resolved: ResolvedDomflaxOptions): StyleResolver {\n if (resolved.provider === 'custom') {\n return createCssResolver([], { files: resolved.cssFiles });\n }\n // 'auto' and 'tailwind' both resolve against the project's Tailwind engine.\n return createTailwindResolver();\n}\n\nexport function createDomflax(options: DomflaxOptions = {}): Domflax {\n const resolved = resolveOptions(options);\n const pipeline = createPipeline();\n const patterns = builtinPatterns;\n\n // Construct the resolver lazily so neither optional engine (Tailwind / postcss) is loaded until a\n // file is actually transformed (and only the engine for the selected provider is ever loaded).\n let cachedResolver: StyleResolver | null = null;\n const getResolver = (): StyleResolver => (cachedResolver ??= createResolver(resolved));\n\n return {\n options: resolved,\n pipeline,\n get resolver(): StyleResolver {\n return getResolver();\n },\n patterns,\n transform(code: string, id: string): DomflaxTransformResult {\n if (!isSupported(id, resolved.include)) return { code, map: null };\n const kind = jsxKindOf(id);\n if (kind !== null) {\n const out = runJsxPipeline(code, id, kind, getResolver(), patterns, resolved.safety);\n return { code: out, map: null };\n }\n // `.html`/`.htm` route to the parse5 HTML frontend/backend (surgical span edits).\n if (htmlKindOf(id) !== null) {\n const out = runHtmlPipeline(code, id, getResolver(), patterns, resolved.safety);\n return { code: out, map: null };\n }\n return { code, map: null };\n },\n };\n}\n\n/* ────────────────────────────────────────────────────────────────────────── *\n * Build adapters (framework-agnostic, structurally-typed shapes)\n * ────────────────────────────────────────────────────────────────────────── */\n\n/**\n * Minimal Vite-plugin shape. Declared locally so this adapter does NOT depend on `vite`'s types\n * (an optional, type-only peer). Structurally compatible with Vite's `Plugin` for the hooks domflax\n * uses: `enforce: 'pre'` runs domflax before Vite's JSX→`createElement` transform, and `transform`\n * is Vite's per-file source hook. Returning `null` is Vite's \"no change\" signal.\n */\nexport interface DomflaxVitePlugin {\n readonly name: string;\n readonly enforce: 'pre';\n /** Vite's per-file source hook. Fully synchronous and browser-free. */\n transform(code: string, id: string): DomflaxTransformResult | null;\n}\n\n/**\n * Vite adapter. Returns a real Vite `Plugin` (`enforce: 'pre'`) whose `transform` runs the domflax\n * engine on `.jsx`/`.tsx` modules — strips any bundler query suffix (e.g. `App.tsx?used`) before\n * matching, returns `{ code, map }` when the source changed, and `null` (Vite's unchanged signal)\n * for unchanged sources and for any non-jsx/tsx module.\n *\n * @example\n * ```js\n * // vite.config.js\n * import domflax from 'domflax';\n * export default { plugins: [domflax.vite({ provider: 'tailwind' })] };\n * ```\n */\nexport function vite(options: DomflaxOptions = {}): DomflaxVitePlugin {\n const engine = createDomflax(options);\n return {\n name: 'domflax',\n enforce: 'pre',\n transform(code: string, id: string): DomflaxTransformResult | null {\n if (!isSupported(id, engine.options.include)) return null;\n const out = engine.transform(code, id);\n // Signal \"no change\" to Vite when the source round-tripped unchanged.\n return out.code === code ? null : out;\n },\n };\n}\n\n/* ── webpack / Next.js ──────────────────────────────────────────────────────────────────────── */\n\n/** A `module.rule` `use` entry: an absolute loader path plus the options forwarded to it. */\ninterface DomflaxRuleUse {\n readonly loader: string;\n readonly options: DomflaxOptions;\n}\n\n/** The slice of a webpack `module.rule` domflax appends. */\ninterface DomflaxModuleRule {\n readonly test: RegExp;\n readonly enforce: 'pre';\n readonly exclude: RegExp;\n readonly use: readonly DomflaxRuleUse[];\n}\n\n/** Anything carrying a `module.rules` array — both a webpack `Compiler.options` and Next's bare config. */\ninterface DomflaxWebpackModuleHost {\n module?: { rules?: unknown[] };\n}\n\n/**\n * Minimal webpack-compiler shape. Declared locally so this adapter does NOT depend on `webpack`'s\n * types. domflax only needs to push a rule onto the host's `module.rules`.\n *\n * `apply` accepts BOTH shapes: a real webpack `Compiler` (rules live under `compiler.options.module`)\n * AND the bare `config` object Next.js hands you from `webpack(config)` (rules live directly under\n * `config.module`). It duck-types `compiler.options ?? compiler` to find the right host.\n */\nexport interface DomflaxWebpackCompiler extends DomflaxWebpackModuleHost {\n options?: DomflaxWebpackModuleHost;\n}\n\n/**\n * Minimal webpack-plugin shape. `apply(compiler)` is the webpack plugin entry point.\n */\nexport interface DomflaxWebpackPlugin {\n readonly name: string;\n apply(compiler: DomflaxWebpackCompiler): void;\n}\n\n/** `.jsx`/`.tsx` modules only (combinator-free with the JSX frontend; `.js`/`.ts` are skipped). */\nconst WEBPACK_JSX_TEST = /\\.[jt]sx$/;\n\n/**\n * Absolute path to the bundled webpack loader (`./webpack-loader`). Resolved lazily against THIS\n * module's location so it works whether `domflax` is loaded as ESM (`dist/index.js`) or CJS\n * (`dist/index.cjs`) — both sit beside `dist/webpack-loader.cjs`. webpack requires loaders via\n * CommonJS, so we always point at the `.cjs` output.\n */\nfunction webpackLoaderPath(): string {\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, 'webpack-loader.cjs');\n}\n\n/**\n * webpack adapter (also the Next.js path). Returns a plugin whose `apply(compiler)` injects a\n * pre-enforced `module.rule` that invokes the domflax {@link ./webpack-loader loader} on every\n * `.jsx`/`.tsx` module. The loader runs the SAME lazy engine as {@link createDomflax} (no eager\n * Tailwind/postcss load).\n *\n * Next.js wiring (`next.config.js`) — Next exposes the underlying webpack config via `webpack(config)`:\n * ```js\n * // next.config.js\n * const domflax = require('domflax');\n * module.exports = {\n * webpack(config) {\n * domflax.webpack({ provider: 'tailwind' }).apply(config);\n * return config;\n * },\n * };\n * ```\n * `apply(compiler)` is intentionally duck-typed on `compiler.options.module.rules`, so it accepts\n * both a real webpack `Compiler` and the bare `config` object Next.js hands you.\n *\n * Caveat: this targets the webpack builder only. **Turbopack is not yet supported** — it does not\n * accept arbitrary webpack loaders, so the `next.config.js` wiring above is a no-op under\n * `next dev --turbopack`. Run domflax through the webpack builder until Turbopack exposes a loader API.\n */\nexport function webpack(options: DomflaxOptions = {}): DomflaxWebpackPlugin {\n // Validate options eagerly (parity with the other adapters); the resolver stays lazy.\n createDomflax(options);\n return {\n name: 'domflax',\n apply(compiler: DomflaxWebpackCompiler): void {\n // Real webpack passes a `Compiler` (rules under `.options.module`); Next's `webpack(config)`\n // passes the bare config (rules under `.module`). Duck-type to the right host.\n const host: DomflaxWebpackModuleHost = compiler.options ?? compiler;\n const mod = (host.module ??= {});\n const rules = (mod.rules ??= []);\n const rule: DomflaxModuleRule = {\n test: WEBPACK_JSX_TEST,\n enforce: 'pre',\n exclude: /node_modules/,\n use: [{ loader: webpackLoaderPath(), options }],\n };\n rules.push(rule);\n },\n };\n}\n\n/**\n * The default-export namespace. Exposes the build adapters and the programmatic factory as an OBJECT\n * so the documented `import domflax from 'domflax'; domflax.vite()` / `domflax.webpack()` works (and a\n * CommonJS `const domflax = require('domflax'); domflax.vite()` too). The named exports\n * (`createDomflax`, `vite`, `webpack`, …) remain available for direct import.\n */\nexport interface DomflaxDefault {\n createDomflax(options?: DomflaxOptions): Domflax;\n vite(options?: DomflaxOptions): DomflaxVitePlugin;\n webpack(options?: DomflaxOptions): DomflaxWebpackPlugin;\n}\n\n/** Default export: an object exposing `vite`, `webpack`, and the programmatic `createDomflax`. */\nconst domflax: DomflaxDefault = { createDomflax, vite, webpack };\nexport default domflax;\n","/**\n * domflax — the single-file JSX/TSX pipeline runner (parse → resolve → flatten → reverse-emit →\n * print), split out of `index.ts` so the meta package's barrel + adapters stay focused.\n *\n * {@link runJsxPipeline} is SYNC and fully static (gate `'provably-safe'`): it never changes rendering\n * and never launches a browser.\n */\n\nimport {\n buildSelectorIndex,\n createSyntheticSink,\n runPasses,\n syncClassesFromComputed,\n} from '@domflax/core';\nimport type {\n ApplyContext,\n FileKind,\n FlattenGate,\n IRDocument,\n Pass,\n PassCategory,\n PassPhase,\n Pattern,\n SafetyLevel,\n StyleResolver,\n} from '@domflax/core';\nimport { createHtmlBackend, createHtmlFrontend } from '@domflax/frontend-html';\nimport { createJsxBackend, createJsxFrontend } from '@domflax/frontend-jsx';\nimport { normalizer } from '@domflax/pattern-kit';\n\n/** `.tsx`/`.jsx` ⇒ the matching {@link FileKind}; anything else ⇒ null (no JSX frontend). */\nexport function jsxKindOf(id: string): FileKind | null {\n const clean = id.split('?', 1)[0] ?? id;\n if (clean.endsWith('.tsx')) return 'tsx';\n if (clean.endsWith('.jsx')) return 'jsx';\n return null;\n}\n\n/** `.html`/`.htm` ⇒ `'html'`; anything else ⇒ null (no HTML frontend). */\nexport function htmlKindOf(id: string): FileKind | null {\n const clean = (id.split('?', 1)[0] ?? id).toLowerCase();\n if (clean.endsWith('.html') || clean.endsWith('.htm')) return 'html';\n return null;\n}\n\n/** First registered source's EOL, defaulting to `\\n`. */\nfunction eolOf(doc: IRDocument): '\\n' | '\\r\\n' {\n for (const src of doc.sources.values()) return src.eol;\n return '\\n';\n}\n\n/** Group the flat pattern list into one {@link Pass} per {@link PassPhase} (derived from category). */\nfunction buildPasses(patterns: readonly Pattern[]): Pass[] {\n const byPhase = new Map<PassPhase, Pattern[]>();\n for (const p of patterns) {\n const phase = (p.category.split('/', 1)[0] ?? 'flatten') as PassPhase;\n let bucket = byPhase.get(phase);\n if (!bucket) {\n bucket = [];\n byPhase.set(phase, bucket);\n }\n bucket.push(p);\n }\n const passes: Pass[] = [];\n for (const [phase, pats] of byPhase) {\n passes.push({ phase, category: `${phase}/builtin` as PassCategory, patterns: pats });\n }\n return passes;\n}\n\n/** The parsed, authorized doc + the apply context + grouped passes, shared by sync + async runs. */\ninterface PreparedRun {\n readonly doc: IRDocument;\n readonly ctx: ApplyContext;\n readonly passes: readonly Pass[];\n}\n\n/** PARSE (JSX → IR, resolving classes onto `computed`) + AUTHORIZE + build the apply context. */\nfunction preparePipeline(\n code: string,\n id: string,\n kind: FileKind,\n resolver: StyleResolver,\n patterns: readonly Pattern[],\n safety: SafetyLevel,\n gate: FlattenGate,\n): PreparedRun {\n const parsed = createJsxFrontend().parse(code, {\n id,\n kind,\n resolver,\n normalizer,\n config: {},\n onDiagnostic: () => {},\n });\n const doc = parsed.doc;\n\n // AUTHORIZE — the JSX frontend defaults every node's safety floor to 0. The orchestrator opens the\n // floor to the max; the configured ceiling + each pattern's opacity predicates are the real gate.\n for (const node of doc.nodes.values()) node.meta.safetyFloor = 3;\n\n const ctx: ApplyContext = {\n doc,\n safetyCeiling: safety,\n normalizer,\n // Real CSS-selector-safety index from the active resolver: a wrapper a combinator/structural\n // selector depends on is flagged so the flatten guards refuse to flatten it. Tailwind (no\n // complexSelectors) degrades to the null index — behaviour unchanged.\n selectors: buildSelectorIndex(doc, resolver),\n resolver,\n gate,\n };\n return { doc, ctx, passes: buildPasses(patterns) };\n}\n\n/** REVERSE-EMIT optimized computed styles back into class tokens, then PRINT IR → JSX/TSX text. */\nfunction finishPipeline(optimized: IRDocument, id: string, resolver: StyleResolver): string {\n syncClassesFromComputed(optimized, resolver, normalizer);\n const printed = createJsxBackend().print(\n optimized,\n { moduleId: id, ops: [], provenance: new Map() },\n {\n normalizer,\n resolver,\n sink: createSyntheticSink(),\n eol: eolOf(optimized),\n onDiagnostic: () => {},\n },\n );\n return printed.code;\n}\n\n/** SYNC full pipeline (gate `'provably-safe'` — never changes rendering, never launches a browser). */\nexport function runJsxPipeline(\n code: string,\n id: string,\n kind: FileKind,\n resolver: StyleResolver,\n patterns: readonly Pattern[],\n safety: SafetyLevel,\n): string {\n const { doc, ctx, passes } = preparePipeline(code, id, kind, resolver, patterns, safety, 'provably-safe');\n const { doc: optimized } = runPasses(doc, passes, ctx);\n return finishPipeline(optimized, id, resolver);\n}\n\n/* ───────────────────────── HTML pipeline (parse5 frontend/backend) ───────────────────────── */\n\n/**\n * PARSE (HTML → IR, resolving classes onto `computed`) + AUTHORIZE + build the apply context. Unlike\n * the JSX path, the HTML frontend sets per-node `safetyFloor` itself (opaque nodes → 0), so we must\n * NOT blanket-open every node to 3 (that would strip the opacity floors).\n */\nfunction prepareHtml(\n code: string,\n id: string,\n resolver: StyleResolver,\n patterns: readonly Pattern[],\n safety: SafetyLevel,\n gate: FlattenGate,\n): PreparedRun {\n const parsed = createHtmlFrontend().parse(code, {\n id,\n kind: 'html',\n resolver,\n normalizer,\n config: {},\n onDiagnostic: () => {},\n });\n const doc = parsed.doc;\n const ctx: ApplyContext = {\n doc,\n safetyCeiling: safety,\n normalizer,\n selectors: buildSelectorIndex(doc, resolver),\n resolver,\n gate,\n };\n return { doc, ctx, passes: buildPasses(patterns) };\n}\n\n/** REVERSE-EMIT optimized computed styles back into class tokens, then PRINT IR → HTML text. */\nfunction finishHtmlPipeline(optimized: IRDocument, id: string, resolver: StyleResolver): string {\n syncClassesFromComputed(optimized, resolver, normalizer);\n const printed = createHtmlBackend().print(\n optimized,\n { moduleId: id, ops: [], provenance: new Map() },\n {\n normalizer,\n resolver,\n sink: createSyntheticSink(),\n eol: eolOf(optimized),\n onDiagnostic: () => {},\n },\n );\n return printed.code;\n}\n\n/** SYNC full HTML pipeline (gate `'provably-safe'` — surgical span edits over verbatim source). */\nexport function runHtmlPipeline(\n code: string,\n id: string,\n resolver: StyleResolver,\n patterns: readonly Pattern[],\n safety: SafetyLevel,\n): string {\n const { doc, ctx, passes } = prepareHtml(code, id, resolver, patterns, safety, 'provably-safe');\n const { doc: optimized } = runPasses(doc, passes, ctx);\n return finishHtmlPipeline(optimized, id, resolver);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import {
|
|
2
|
+
builtinPatterns,
|
|
3
|
+
createCssResolver,
|
|
4
|
+
createHtmlBackend,
|
|
5
|
+
createHtmlFrontend,
|
|
6
|
+
createJsxBackend,
|
|
7
|
+
createJsxFrontend,
|
|
8
|
+
createTailwindResolver
|
|
9
|
+
} from "./chunk-3Z5ZWLXX.js";
|
|
10
|
+
import {
|
|
11
|
+
buildSelectorIndex,
|
|
12
|
+
createSyntheticSink,
|
|
13
|
+
normalizer,
|
|
14
|
+
runPasses,
|
|
15
|
+
syncClassesFromComputed
|
|
16
|
+
} from "./chunk-H5KTGI3A.js";
|
|
17
|
+
import {
|
|
18
|
+
init_esm_shims
|
|
19
|
+
} from "./chunk-U5GOONKV.js";
|
|
20
|
+
|
|
21
|
+
// ../cli/src/safety.ts
|
|
22
|
+
init_esm_shims();
|
|
23
|
+
import { execFileSync } from "child_process";
|
|
24
|
+
import * as path from "path";
|
|
25
|
+
var DISPOSABLE_DIRS = /* @__PURE__ */ new Set(["dist", "build", "out", ".next"]);
|
|
26
|
+
function isDisposablePath(file) {
|
|
27
|
+
return path.resolve(file).split(path.sep).some((seg) => DISPOSABLE_DIRS.has(seg));
|
|
28
|
+
}
|
|
29
|
+
function isGitClean(cwd) {
|
|
30
|
+
try {
|
|
31
|
+
const out = execFileSync("git", ["status", "--porcelain"], {
|
|
32
|
+
cwd,
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
35
|
+
});
|
|
36
|
+
return out.trim().length === 0;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function planWrites(options, gitClean) {
|
|
42
|
+
if (options.dangerouslyOverwriteSource) {
|
|
43
|
+
if (!options.noGitCheck && !gitClean) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
error: "refusing --dangerously-overwrite-source: git working tree is not clean. Commit or stash first, or pass --no-git-check to override."
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return { ok: true, value: { mode: "overwrite-source", outDir: null } };
|
|
50
|
+
}
|
|
51
|
+
const outDir = path.resolve(options.out ?? "domflax-out");
|
|
52
|
+
return { ok: true, value: { mode: "out-dir", outDir } };
|
|
53
|
+
}
|
|
54
|
+
function destinationFor(file, inputRoot, plan) {
|
|
55
|
+
const absFile = path.resolve(file);
|
|
56
|
+
if (plan.mode === "overwrite-source") {
|
|
57
|
+
return { ok: true, value: absFile };
|
|
58
|
+
}
|
|
59
|
+
const outDir = plan.outDir;
|
|
60
|
+
const rel = path.relative(inputRoot, absFile);
|
|
61
|
+
const safeRel = rel === "" || rel.startsWith("..") || path.isAbsolute(rel) ? path.basename(absFile) : rel;
|
|
62
|
+
const dest = path.join(outDir, safeRel);
|
|
63
|
+
if (path.resolve(dest) === absFile && !isDisposablePath(absFile)) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
error: `refusing to overwrite source file ${absFile}: the output path resolves onto the source. Choose a different --out, or pass --dangerously-overwrite-source (with a clean git tree).`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, value: dest };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ../cli/src/transform.ts
|
|
73
|
+
init_esm_shims();
|
|
74
|
+
import * as path3 from "path";
|
|
75
|
+
|
|
76
|
+
// ../cli/src/html-css.ts
|
|
77
|
+
init_esm_shims();
|
|
78
|
+
import { createHash } from "crypto";
|
|
79
|
+
import { existsSync } from "fs";
|
|
80
|
+
import * as path2 from "path";
|
|
81
|
+
function isRemoteHref(href) {
|
|
82
|
+
const h = href.trim();
|
|
83
|
+
return h.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(h);
|
|
84
|
+
}
|
|
85
|
+
function attrValue(tag, name) {
|
|
86
|
+
const re = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'>]+))`, "i");
|
|
87
|
+
const m = re.exec(tag);
|
|
88
|
+
if (!m) return null;
|
|
89
|
+
return (m[1] ?? m[2] ?? m[3] ?? "").trim();
|
|
90
|
+
}
|
|
91
|
+
function extractLinkHrefs(html) {
|
|
92
|
+
const out = [];
|
|
93
|
+
const linkRe = /<link\b[^>]*>/gi;
|
|
94
|
+
let m;
|
|
95
|
+
while ((m = linkRe.exec(html)) !== null) {
|
|
96
|
+
const tag = m[0];
|
|
97
|
+
const rel = attrValue(tag, "rel");
|
|
98
|
+
if (rel === null || !/(?:^|\s)stylesheet(?:\s|$)/i.test(rel)) continue;
|
|
99
|
+
const href = attrValue(tag, "href");
|
|
100
|
+
if (href) out.push(href);
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function extractInlineStyles(html) {
|
|
105
|
+
const out = [];
|
|
106
|
+
const styleRe = /<style\b([^>]*)>([\s\S]*?)<\/style>/gi;
|
|
107
|
+
let m;
|
|
108
|
+
while ((m = styleRe.exec(html)) !== null) {
|
|
109
|
+
const type = attrValue(`<style ${m[1] ?? ""}>`, "type")?.toLowerCase();
|
|
110
|
+
if (type && type !== "text/css" && type !== "css") continue;
|
|
111
|
+
const css = m[2] ?? "";
|
|
112
|
+
if (css.trim().length > 0) out.push(css);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function extractHtmlStylesheets(htmlCode, htmlAbsPath) {
|
|
117
|
+
const dir = path2.dirname(path2.resolve(htmlAbsPath));
|
|
118
|
+
const files = [];
|
|
119
|
+
const seen = /* @__PURE__ */ new Set();
|
|
120
|
+
for (const rawHref of extractLinkHrefs(htmlCode)) {
|
|
121
|
+
const href = (rawHref.split(/[?#]/, 1)[0] ?? "").trim();
|
|
122
|
+
if (!href || isRemoteHref(href)) continue;
|
|
123
|
+
const abs = path2.resolve(dir, href);
|
|
124
|
+
if (seen.has(abs)) continue;
|
|
125
|
+
seen.add(abs);
|
|
126
|
+
if (existsSync(abs)) files.push(abs);
|
|
127
|
+
}
|
|
128
|
+
return { files, inline: extractInlineStyles(htmlCode) };
|
|
129
|
+
}
|
|
130
|
+
function cssSetKey(sortedResolvedPaths, inline) {
|
|
131
|
+
const h = createHash("sha1");
|
|
132
|
+
h.update(sortedResolvedPaths.join("\n"));
|
|
133
|
+
h.update("\0inline\0");
|
|
134
|
+
for (const block of inline) {
|
|
135
|
+
h.update(block);
|
|
136
|
+
h.update("\0");
|
|
137
|
+
}
|
|
138
|
+
return h.digest("hex");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ../cli/src/transform.ts
|
|
142
|
+
function buildResolver(provider, css, projectRoot) {
|
|
143
|
+
if (provider === "custom") {
|
|
144
|
+
return createCssResolver([], { files: css, projectRoot });
|
|
145
|
+
}
|
|
146
|
+
return createTailwindResolver({ projectRoot });
|
|
147
|
+
}
|
|
148
|
+
function buildPasses(patterns) {
|
|
149
|
+
const byPhase = /* @__PURE__ */ new Map();
|
|
150
|
+
for (const p of patterns) {
|
|
151
|
+
const phase = p.category.split("/", 1)[0] ?? "flatten";
|
|
152
|
+
let bucket = byPhase.get(phase);
|
|
153
|
+
if (!bucket) {
|
|
154
|
+
bucket = [];
|
|
155
|
+
byPhase.set(phase, bucket);
|
|
156
|
+
}
|
|
157
|
+
bucket.push(p);
|
|
158
|
+
}
|
|
159
|
+
const passes = [];
|
|
160
|
+
for (const [phase, pats] of byPhase) {
|
|
161
|
+
passes.push({ phase, category: `${phase}/builtin`, patterns: pats });
|
|
162
|
+
}
|
|
163
|
+
return passes;
|
|
164
|
+
}
|
|
165
|
+
function selectPatterns(names) {
|
|
166
|
+
if (names === null) return builtinPatterns;
|
|
167
|
+
const set = new Set(names);
|
|
168
|
+
return builtinPatterns.filter((p) => set.has(p.name));
|
|
169
|
+
}
|
|
170
|
+
function jsxKindOf(id) {
|
|
171
|
+
const clean = id.split("?", 1)[0] ?? id;
|
|
172
|
+
const lower = clean.toLowerCase();
|
|
173
|
+
if (lower.endsWith(".tsx")) return "tsx";
|
|
174
|
+
if (lower.endsWith(".jsx")) return "jsx";
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
function htmlKindOf(id) {
|
|
178
|
+
const lower = (id.split("?", 1)[0] ?? id).toLowerCase();
|
|
179
|
+
if (lower.endsWith(".html") || lower.endsWith(".htm")) return "html";
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
function countClassTokens(code) {
|
|
183
|
+
let total = 0;
|
|
184
|
+
const re = /\b(?:className|class)\s*=\s*"([^"]*)"/g;
|
|
185
|
+
let m;
|
|
186
|
+
while ((m = re.exec(code)) !== null) {
|
|
187
|
+
total += m[1].split(/\s+/).filter((t) => t.length > 0).length;
|
|
188
|
+
}
|
|
189
|
+
return total;
|
|
190
|
+
}
|
|
191
|
+
function bytes(s) {
|
|
192
|
+
return Buffer.byteLength(s, "utf8");
|
|
193
|
+
}
|
|
194
|
+
function passthroughResult(code) {
|
|
195
|
+
return {
|
|
196
|
+
code,
|
|
197
|
+
changed: false,
|
|
198
|
+
passthrough: true,
|
|
199
|
+
stats: {
|
|
200
|
+
nodesIn: 0,
|
|
201
|
+
nodesOut: 0,
|
|
202
|
+
nodesRemoved: 0,
|
|
203
|
+
classesBefore: 0,
|
|
204
|
+
classesAfter: 0,
|
|
205
|
+
classesSaved: 0,
|
|
206
|
+
bytesBefore: bytes(code),
|
|
207
|
+
bytesAfter: bytes(code),
|
|
208
|
+
bytesSaved: 0
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function createTransform(options) {
|
|
213
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
214
|
+
const globalResolver = buildResolver(options.provider, options.css, projectRoot);
|
|
215
|
+
const patterns = selectPatterns(options.passes);
|
|
216
|
+
const resolverCache = /* @__PURE__ */ new Map();
|
|
217
|
+
function resolverFor(code, id) {
|
|
218
|
+
if (options.provider !== "custom" || htmlKindOf(id) === null) return globalResolver;
|
|
219
|
+
const { files: localFiles, inline } = extractHtmlStylesheets(code, id);
|
|
220
|
+
if (localFiles.length === 0 && inline.length === 0) return globalResolver;
|
|
221
|
+
const globalPaths = options.css.map((p) => path3.resolve(p));
|
|
222
|
+
const sortedPaths = [.../* @__PURE__ */ new Set([...globalPaths, ...localFiles])].sort();
|
|
223
|
+
const key = cssSetKey(sortedPaths, inline);
|
|
224
|
+
let resolver = resolverCache.get(key);
|
|
225
|
+
if (!resolver) {
|
|
226
|
+
const inlineFiles = inline.map((css, i) => ({ id: `${id}#inline-${i}`, css }));
|
|
227
|
+
resolver = createCssResolver(inlineFiles, { files: sortedPaths, projectRoot });
|
|
228
|
+
resolverCache.set(key, resolver);
|
|
229
|
+
}
|
|
230
|
+
return resolver;
|
|
231
|
+
}
|
|
232
|
+
function prepare(code, id, kind, gate, resolver) {
|
|
233
|
+
const parsed = createJsxFrontend().parse(code, {
|
|
234
|
+
id,
|
|
235
|
+
kind,
|
|
236
|
+
resolver,
|
|
237
|
+
normalizer,
|
|
238
|
+
config: {},
|
|
239
|
+
onDiagnostic: () => {
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
const doc = parsed.doc;
|
|
243
|
+
const nodesIn = doc.nodes.size;
|
|
244
|
+
for (const node of doc.nodes.values()) node.meta.safetyFloor = 3;
|
|
245
|
+
const ctx = {
|
|
246
|
+
doc,
|
|
247
|
+
safetyCeiling: options.safety,
|
|
248
|
+
normalizer,
|
|
249
|
+
// Real CSS-selector-safety index from the active resolver (custom-CSS reports combinator /
|
|
250
|
+
// structural-pseudo coupling; Tailwind has none → null index, behaviour unchanged).
|
|
251
|
+
selectors: buildSelectorIndex(doc, resolver),
|
|
252
|
+
resolver,
|
|
253
|
+
gate
|
|
254
|
+
};
|
|
255
|
+
return { doc, ctx, passes: buildPasses(patterns), nodesIn };
|
|
256
|
+
}
|
|
257
|
+
function prepareHtml(code, id, gate, resolver) {
|
|
258
|
+
const parsed = createHtmlFrontend().parse(code, {
|
|
259
|
+
id,
|
|
260
|
+
kind: "html",
|
|
261
|
+
resolver,
|
|
262
|
+
normalizer,
|
|
263
|
+
config: {},
|
|
264
|
+
onDiagnostic: () => {
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
const doc = parsed.doc;
|
|
268
|
+
const nodesIn = doc.nodes.size;
|
|
269
|
+
const ctx = {
|
|
270
|
+
doc,
|
|
271
|
+
safetyCeiling: options.safety,
|
|
272
|
+
normalizer,
|
|
273
|
+
selectors: buildSelectorIndex(doc, resolver),
|
|
274
|
+
resolver,
|
|
275
|
+
gate
|
|
276
|
+
};
|
|
277
|
+
return { doc, ctx, passes: buildPasses(patterns), nodesIn };
|
|
278
|
+
}
|
|
279
|
+
function finish(code, optimized, id, nodesIn, resolver, backend = "jsx") {
|
|
280
|
+
syncClassesFromComputed(optimized, resolver, normalizer);
|
|
281
|
+
const print = backend === "html" ? createHtmlBackend().print : createJsxBackend().print;
|
|
282
|
+
const printed = print(
|
|
283
|
+
optimized,
|
|
284
|
+
{ moduleId: id, ops: [], provenance: /* @__PURE__ */ new Map() },
|
|
285
|
+
{ normalizer, resolver, sink: createSyntheticSink(), eol: "\n", onDiagnostic: () => {
|
|
286
|
+
} }
|
|
287
|
+
);
|
|
288
|
+
const out = printed.code;
|
|
289
|
+
const nodesOut = optimized.nodes.size;
|
|
290
|
+
const classesBefore = countClassTokens(code);
|
|
291
|
+
const classesAfter = countClassTokens(out);
|
|
292
|
+
return {
|
|
293
|
+
code: out,
|
|
294
|
+
changed: out !== code,
|
|
295
|
+
passthrough: false,
|
|
296
|
+
stats: {
|
|
297
|
+
nodesIn,
|
|
298
|
+
nodesOut,
|
|
299
|
+
nodesRemoved: Math.max(0, nodesIn - nodesOut),
|
|
300
|
+
classesBefore,
|
|
301
|
+
classesAfter,
|
|
302
|
+
classesSaved: Math.max(0, classesBefore - classesAfter),
|
|
303
|
+
bytesBefore: bytes(code),
|
|
304
|
+
bytesAfter: bytes(out),
|
|
305
|
+
bytesSaved: bytes(code) - bytes(out)
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
resolver: globalResolver,
|
|
311
|
+
transformFile(code, id) {
|
|
312
|
+
const kind = jsxKindOf(id);
|
|
313
|
+
if (kind !== null) {
|
|
314
|
+
const resolver = resolverFor(code, id);
|
|
315
|
+
const { doc, ctx, passes, nodesIn } = prepare(code, id, kind, "provably-safe", resolver);
|
|
316
|
+
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
317
|
+
return finish(code, optimized, id, nodesIn, resolver);
|
|
318
|
+
}
|
|
319
|
+
if (htmlKindOf(id) !== null) {
|
|
320
|
+
const resolver = resolverFor(code, id);
|
|
321
|
+
const { doc, ctx, passes, nodesIn } = prepareHtml(code, id, "provably-safe", resolver);
|
|
322
|
+
const { doc: optimized } = runPasses(doc, passes, ctx);
|
|
323
|
+
return finish(code, optimized, id, nodesIn, resolver, "html");
|
|
324
|
+
}
|
|
325
|
+
return passthroughResult(code);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export {
|
|
331
|
+
isGitClean,
|
|
332
|
+
planWrites,
|
|
333
|
+
destinationFor,
|
|
334
|
+
createTransform
|
|
335
|
+
};
|
|
336
|
+
//# sourceMappingURL=chunk-EVENAJYI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../cli/src/safety.ts","../../cli/src/transform.ts","../../cli/src/html-css.ts"],"sourcesContent":["/**\n * @domflax/cli — OUTPUT SAFETY (DESIGN-DECISIONS Q16, ARCHITECTURE §16.10).\n *\n * Source is READ-ONLY by default. Writes land in `--out`/`./domflax-out` (mirroring structure), or in\n * place ONLY inside disposable build dirs (dist/build/out/.next). Overwriting real source in place\n * requires `--dangerously-overwrite-source` AND a clean git tree (skippable with `--no-git-check`).\n * `--dry-run` writes nothing.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport * as path from 'node:path';\n\nimport type { CliOptions } from './options';\n\n/** Disposable build directories where in-place overwrite is always safe (they are regenerated). */\nconst DISPOSABLE_DIRS: ReadonlySet<string> = new Set(['dist', 'build', 'out', '.next']);\n\n/** True when any path segment is a disposable build dir, so the file is a regenerable artifact. */\nexport function isDisposablePath(file: string): boolean {\n return path\n .resolve(file)\n .split(path.sep)\n .some((seg) => DISPOSABLE_DIRS.has(seg));\n}\n\nexport type WriteMode = 'out-dir' | 'overwrite-source';\n\n/** Invocation-level write plan shared by every file. */\nexport interface WritePlan {\n readonly mode: WriteMode;\n /** Resolved absolute output dir for `out-dir` mode; `null` when overwriting source in place. */\n readonly outDir: string | null;\n}\n\nexport type Result<T> = { readonly ok: true; readonly value: T } | { readonly ok: false; readonly error: string };\n\n/** Run `git status --porcelain`; clean ⇒ true. A non-repo / missing git ⇒ false (fail safe). */\nexport function isGitClean(cwd: string): boolean {\n try {\n const out = execFileSync('git', ['status', '--porcelain'], {\n cwd,\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n return out.trim().length === 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the invocation-level {@link WritePlan}. In-place source overwrite is refused unless the\n * danger flag is set and (the git tree is clean OR the git check is waived).\n */\nexport function planWrites(options: CliOptions, gitClean: boolean): Result<WritePlan> {\n if (options.dangerouslyOverwriteSource) {\n if (!options.noGitCheck && !gitClean) {\n return {\n ok: false,\n error:\n 'refusing --dangerously-overwrite-source: git working tree is not clean. ' +\n 'Commit or stash first, or pass --no-git-check to override.',\n };\n }\n return { ok: true, value: { mode: 'overwrite-source', outDir: null } };\n }\n const outDir = path.resolve(options.out ?? 'domflax-out');\n return { ok: true, value: { mode: 'out-dir', outDir } };\n}\n\n/**\n * Compute the destination path for one source file under a {@link WritePlan}. Refuses when an\n * `out-dir` destination resolves onto the source file itself and that file is NOT a disposable build\n * artifact — that would be an unsanctioned in-place source overwrite (the Q16 guard).\n */\nexport function destinationFor(file: string, inputRoot: string, plan: WritePlan): Result<string> {\n const absFile = path.resolve(file);\n\n if (plan.mode === 'overwrite-source') {\n // Already gated by planWrites (danger flag + clean-git / waiver).\n return { ok: true, value: absFile };\n }\n\n const outDir = plan.outDir!;\n const rel = path.relative(inputRoot, absFile);\n // Inputs outside the mirror root collapse to their basename so we never escape outDir with `..`.\n const safeRel = rel === '' || rel.startsWith('..') || path.isAbsolute(rel) ? path.basename(absFile) : rel;\n const dest = path.join(outDir, safeRel);\n\n if (path.resolve(dest) === absFile && !isDisposablePath(absFile)) {\n return {\n ok: false,\n error:\n `refusing to overwrite source file ${absFile}: the output path resolves onto the source. ` +\n 'Choose a different --out, or pass --dangerously-overwrite-source (with a clean git tree).',\n };\n }\n return { ok: true, value: dest };\n}\n","/**\n * @domflax/cli — the single-file transform engine.\n *\n * Built directly from the LOWER packages (core + frontend-jsx + resolver-tailwind/resolver-css +\n * patterns + pattern-kit). It deliberately does NOT import the `domflax` meta package: domflax's bin\n * imports `@domflax/cli`, so importing domflax here would form a dependency cycle. The pipeline\n * mirrors domflax's own: parse (JSX→IR, resolving each element's static classes through the chosen\n * resolver) → runPasses(builtinPatterns) → reverse-emit computed styles back to class tokens → print.\n *\n * `.jsx`/`.tsx` route to `@domflax/frontend-jsx` (Babel); `.html`/`.htm` route to\n * `@domflax/frontend-html` (parse5). Every other file passes through unchanged.\n */\n\nimport {\n buildSelectorIndex,\n createSyntheticSink,\n runPasses,\n syncClassesFromComputed,\n} from '@domflax/core';\nimport type {\n ApplyContext,\n FileKind,\n FlattenGate,\n IRDocument,\n Pass,\n PassCategory,\n PassPhase,\n Pattern,\n SafetyLevel,\n StyleResolver,\n} from '@domflax/core';\nimport { createHtmlBackend, createHtmlFrontend } from '@domflax/frontend-html';\nimport { createJsxBackend, createJsxFrontend } from '@domflax/frontend-jsx';\nimport { normalizer } from '@domflax/pattern-kit';\nimport { builtinPatterns } from '@domflax/patterns';\nimport { createCssResolver } from '@domflax/resolver-css';\nimport { createTailwindResolver } from '@domflax/resolver-tailwind';\n\nimport * as path from 'node:path';\n\nimport { cssSetKey, extractHtmlStylesheets } from './html-css';\nimport type { CliOptions, ProviderOption } from './options';\n\n/* ───────────────────────── per-file result + stats ───────────────────────── */\n\nexport interface FileStats {\n readonly nodesIn: number;\n readonly nodesOut: number;\n readonly nodesRemoved: number;\n readonly classesBefore: number;\n readonly classesAfter: number;\n readonly classesSaved: number;\n readonly bytesBefore: number;\n readonly bytesAfter: number;\n readonly bytesSaved: number;\n}\n\nexport interface FileResult {\n readonly code: string;\n readonly changed: boolean;\n readonly passthrough: boolean;\n readonly stats: FileStats;\n}\n\n/** A configured transform — holds the resolver (and its cached engine) across files. */\nexport interface Transform {\n readonly resolver: StyleResolver;\n /**\n * SYNC transform — fully static (gate `'provably-safe'`); never changes rendering and never launches\n * a browser. Only provably layout-neutral flattens are applied.\n */\n transformFile(code: string, id: string): FileResult;\n}\n\n/* ───────────────────────── resolver wiring ───────────────────────── */\n\n/**\n * Build the {@link StyleResolver} for the chosen provider. The heavy engine each wraps (Tailwind v3 /\n * postcss) is loaded LAZILY at construction and resolved from the user's PROJECT via the factories'\n * `projectRoot` option — never from where the CLI bundle happens to live.\n */\nexport function buildResolver(provider: ProviderOption, css: readonly string[], projectRoot: string): StyleResolver {\n if (provider === 'custom') {\n return createCssResolver([], { files: css, projectRoot });\n }\n // 'auto' and 'tailwind' both resolve against the project's Tailwind engine.\n return createTailwindResolver({ projectRoot });\n}\n\n/* ───────────────────────── pass assembly ───────────────────────── */\n\n/** Group the flat pattern list into one {@link Pass} per {@link PassPhase} (derived from category). */\nfunction buildPasses(patterns: readonly Pattern[]): Pass[] {\n const byPhase = new Map<PassPhase, Pattern[]>();\n for (const p of patterns) {\n const phase = (p.category.split('/', 1)[0] ?? 'flatten') as PassPhase;\n let bucket = byPhase.get(phase);\n if (!bucket) {\n bucket = [];\n byPhase.set(phase, bucket);\n }\n bucket.push(p);\n }\n const passes: Pass[] = [];\n for (const [phase, pats] of byPhase) {\n passes.push({ phase, category: `${phase}/builtin` as PassCategory, patterns: pats });\n }\n return passes;\n}\n\n/** Select the active patterns: every built-in unless the caller narrowed by name (the wizard does). */\nfunction selectPatterns(names: readonly string[] | null): readonly Pattern[] {\n if (names === null) return builtinPatterns;\n const set = new Set(names);\n return builtinPatterns.filter((p) => set.has(p.name));\n}\n\n/* ───────────────────────── file kind + token counting ───────────────────────── */\n\n/** `.tsx`/`.jsx` ⇒ the matching {@link FileKind}; anything else ⇒ null (no JSX frontend). */\nfunction jsxKindOf(id: string): FileKind | null {\n const clean = id.split('?', 1)[0] ?? id;\n const lower = clean.toLowerCase();\n if (lower.endsWith('.tsx')) return 'tsx';\n if (lower.endsWith('.jsx')) return 'jsx';\n return null;\n}\n\n/** `.html`/`.htm` ⇒ `'html'`; anything else ⇒ null (no HTML frontend). */\nfunction htmlKindOf(id: string): FileKind | null {\n const lower = (id.split('?', 1)[0] ?? id).toLowerCase();\n if (lower.endsWith('.html') || lower.endsWith('.htm')) return 'html';\n return null;\n}\n\n/** Rough class-token count for the `--report` summary (provider-independent, string-level). */\nfunction countClassTokens(code: string): number {\n let total = 0;\n const re = /\\b(?:className|class)\\s*=\\s*\"([^\"]*)\"/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(code)) !== null) {\n total += m[1]!.split(/\\s+/).filter((t) => t.length > 0).length;\n }\n return total;\n}\n\nfunction bytes(s: string): number {\n return Buffer.byteLength(s, 'utf8');\n}\n\nfunction passthroughResult(code: string): FileResult {\n return {\n code,\n changed: false,\n passthrough: true,\n stats: {\n nodesIn: 0,\n nodesOut: 0,\n nodesRemoved: 0,\n classesBefore: 0,\n classesAfter: 0,\n classesSaved: 0,\n bytesBefore: bytes(code),\n bytesAfter: bytes(code),\n bytesSaved: 0,\n },\n };\n}\n\n/* ───────────────────────── the transform ───────────────────────── */\n\n/**\n * Construct a transform for the given options. The resolver (and its engine) is built once and reused\n * across every file. With `provider: 'tailwind'|'auto'`, if Tailwind cannot be resolved from the\n * project the resolver degrades to resolving nothing — transforms then pass through unchanged.\n */\n/** Parsed + authorized doc and the apply context, shared by the sync + async transform paths. */\ninterface PreparedFile {\n readonly doc: IRDocument;\n readonly ctx: ApplyContext;\n readonly passes: readonly Pass[];\n readonly nodesIn: number;\n}\n\nexport function createTransform(options: CliOptions): Transform {\n const projectRoot = options.projectRoot ?? process.cwd();\n const globalResolver = buildResolver(options.provider, options.css, projectRoot);\n const patterns = selectPatterns(options.passes);\n\n // FEATURE A — per-file resolver cache, keyed by the exact CSS set (sorted paths + inline hash), so\n // pages that share stylesheet imports reuse one resolver (and its parsed engine).\n const resolverCache = new Map<string, StyleResolver>();\n\n /**\n * Choose the resolver for one file. `.jsx`/`.tsx` and every non-custom provider use the single\n * GLOBAL resolver. An `.html`/`.htm` file under the CUSTOM provider resolves against the GLOBAL set\n * (`options.css`, applied to every file) PLUS its own `<link>` imports and inline `<style>` blocks.\n */\n function resolverFor(code: string, id: string): StyleResolver {\n if (options.provider !== 'custom' || htmlKindOf(id) === null) return globalResolver;\n const { files: localFiles, inline } = extractHtmlStylesheets(code, id);\n if (localFiles.length === 0 && inline.length === 0) return globalResolver;\n\n // Canonicalize: global stylesheets first (base cascade), then the file's own imports. The cache\n // key — and the resolver's own file order — are the sorted, resolved paths, so identical import\n // sets always hit the same cache entry regardless of source ordering.\n const globalPaths = options.css.map((p) => path.resolve(p));\n const sortedPaths = [...new Set([...globalPaths, ...localFiles])].sort();\n const key = cssSetKey(sortedPaths, inline);\n\n let resolver = resolverCache.get(key);\n if (!resolver) {\n const inlineFiles = inline.map((css, i) => ({ id: `${id}#inline-${i}`, css }));\n resolver = createCssResolver(inlineFiles, { files: sortedPaths, projectRoot });\n resolverCache.set(key, resolver);\n }\n return resolver;\n }\n\n /** PARSE (JSX → IR, classes onto `computed`) + AUTHORIZE + build the apply context for `gate`. */\n function prepare(\n code: string,\n id: string,\n kind: FileKind,\n gate: FlattenGate,\n resolver: StyleResolver,\n ): PreparedFile {\n const parsed = createJsxFrontend().parse(code, {\n id,\n kind,\n resolver,\n normalizer,\n config: {},\n onDiagnostic: () => {},\n });\n const doc = parsed.doc;\n const nodesIn = doc.nodes.size;\n for (const node of doc.nodes.values()) node.meta.safetyFloor = 3;\n const ctx: ApplyContext = {\n doc,\n safetyCeiling: options.safety as SafetyLevel,\n normalizer,\n // Real CSS-selector-safety index from the active resolver (custom-CSS reports combinator /\n // structural-pseudo coupling; Tailwind has none → null index, behaviour unchanged).\n selectors: buildSelectorIndex(doc, resolver),\n resolver,\n gate,\n };\n return { doc, ctx, passes: buildPasses(patterns), nodesIn };\n }\n\n /**\n * PARSE (HTML → IR, classes onto `computed`) for `.html`/`.htm`. The HTML frontend sets per-node\n * safety floors itself (opaque nodes → 0), so — unlike the JSX prepare — we must NOT blanket-open\n * every node to floor 3.\n */\n function prepareHtml(code: string, id: string, gate: FlattenGate, resolver: StyleResolver): PreparedFile {\n const parsed = createHtmlFrontend().parse(code, {\n id,\n kind: 'html',\n resolver,\n normalizer,\n config: {},\n onDiagnostic: () => {},\n });\n const doc = parsed.doc;\n const nodesIn = doc.nodes.size;\n const ctx: ApplyContext = {\n doc,\n safetyCeiling: options.safety as SafetyLevel,\n normalizer,\n selectors: buildSelectorIndex(doc, resolver),\n resolver,\n gate,\n };\n return { doc, ctx, passes: buildPasses(patterns), nodesIn };\n }\n\n /** REVERSE-EMIT + PRINT the optimized doc, then assemble the per-file result + stats. */\n function finish(\n code: string,\n optimized: IRDocument,\n id: string,\n nodesIn: number,\n resolver: StyleResolver,\n backend: 'jsx' | 'html' = 'jsx',\n ): FileResult {\n syncClassesFromComputed(optimized, resolver, normalizer);\n const print = backend === 'html' ? createHtmlBackend().print : createJsxBackend().print;\n const printed = print(\n optimized,\n { moduleId: id, ops: [], provenance: new Map() },\n { normalizer, resolver, sink: createSyntheticSink(), eol: '\\n', onDiagnostic: () => {} },\n );\n const out = printed.code;\n const nodesOut = optimized.nodes.size;\n const classesBefore = countClassTokens(code);\n const classesAfter = countClassTokens(out);\n return {\n code: out,\n changed: out !== code,\n passthrough: false,\n stats: {\n nodesIn,\n nodesOut,\n nodesRemoved: Math.max(0, nodesIn - nodesOut),\n classesBefore,\n classesAfter,\n classesSaved: Math.max(0, classesBefore - classesAfter),\n bytesBefore: bytes(code),\n bytesAfter: bytes(out),\n bytesSaved: bytes(code) - bytes(out),\n },\n };\n }\n\n return {\n resolver: globalResolver,\n transformFile(code: string, id: string): FileResult {\n const kind = jsxKindOf(id);\n if (kind !== null) {\n const resolver = resolverFor(code, id);\n const { doc, ctx, passes, nodesIn } = prepare(code, id, kind, 'provably-safe', resolver);\n const { doc: optimized } = runPasses(doc, passes, ctx);\n return finish(code, optimized, id, nodesIn, resolver);\n }\n if (htmlKindOf(id) !== null) {\n const resolver = resolverFor(code, id);\n const { doc, ctx, passes, nodesIn } = prepareHtml(code, id, 'provably-safe', resolver);\n const { doc: optimized } = runPasses(doc, passes, ctx);\n return finish(code, optimized, id, nodesIn, resolver, 'html');\n }\n return passthroughResult(code);\n },\n };\n}\n\n/** The names of every built-in pattern, for the wizard's multiselect. */\nexport function builtinPatternNames(): readonly string[] {\n return builtinPatterns.map((p) => p.name);\n}\n","/**\n * @domflax/cli — per-file CSS discovery for HTML inputs (FEATURE A).\n *\n * Each HTML page links its OWN stylesheets, so resolving every file against one hand-picked global\n * `--css` set is wrong for HTML. {@link extractHtmlStylesheets} lifts a file's own local stylesheets\n * (`<link rel=\"stylesheet\" href>`, resolved RELATIVE to the html file's directory) plus its inline\n * `<style>` blocks straight out of the markup. Remote (`http:`/`https:`/`data:`/protocol-relative)\n * and missing hrefs are skipped, so only real, on-disk stylesheets feed the resolver.\n *\n * The scan is a lightweight, dependency-free `<link>`/`<style>` sweep (no parse5): it runs per file and\n * inside every worker, so keeping it self-contained avoids pulling the HTML frontend into the hot path.\n * {@link cssSetKey} keys the transform's resolver cache so pages sharing imports reuse one resolver.\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport * as path from 'node:path';\n\n/** The stylesheets a single HTML file pulls in beyond the global set. */\nexport interface HtmlStylesheets {\n /** Absolute paths of local `<link rel=\"stylesheet\">` targets that exist on disk. */\n readonly files: readonly string[];\n /** Verbatim contents of the file's inline `<style>` blocks (source order). */\n readonly inline: readonly string[];\n}\n\n/** True for any href we must NOT treat as a local file: it has a URI scheme or is protocol-relative. */\nfunction isRemoteHref(href: string): boolean {\n const h = href.trim();\n // A leading scheme (`http:`, `https:`, `data:`, `file:`…) or `//host` protocol-relative form.\n return h.startsWith('//') || /^[a-z][a-z0-9+.-]*:/i.test(h);\n}\n\n/**\n * Read one attribute's value out of a start-tag string. Handles double-, single-, and unquoted forms;\n * returns `null` when the attribute is absent (an empty value returns `''`).\n */\nfunction attrValue(tag: string, name: string): string | null {\n const re = new RegExp(`\\\\b${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s\"'>]+))`, 'i');\n const m = re.exec(tag);\n if (!m) return null;\n return (m[1] ?? m[2] ?? m[3] ?? '').trim();\n}\n\n/** Collect the `href` of every `<link>` whose `rel` includes the `stylesheet` keyword. */\nfunction extractLinkHrefs(html: string): string[] {\n const out: string[] = [];\n const linkRe = /<link\\b[^>]*>/gi;\n let m: RegExpExecArray | null;\n while ((m = linkRe.exec(html)) !== null) {\n const tag = m[0];\n const rel = attrValue(tag, 'rel');\n if (rel === null || !/(?:^|\\s)stylesheet(?:\\s|$)/i.test(rel)) continue;\n const href = attrValue(tag, 'href');\n if (href) out.push(href);\n }\n return out;\n}\n\n/** Collect the text of every inline `<style>` block (skipping any explicitly non-CSS `type`). */\nfunction extractInlineStyles(html: string): string[] {\n const out: string[] = [];\n const styleRe = /<style\\b([^>]*)>([\\s\\S]*?)<\\/style>/gi;\n let m: RegExpExecArray | null;\n while ((m = styleRe.exec(html)) !== null) {\n const type = attrValue(`<style ${m[1] ?? ''}>`, 'type')?.toLowerCase();\n // Only plain CSS counts — a typed island like text/scss or text/template is not a stylesheet.\n if (type && type !== 'text/css' && type !== 'css') continue;\n const css = m[2] ?? '';\n if (css.trim().length > 0) out.push(css);\n }\n return out;\n}\n\n/**\n * Extract an HTML file's own local stylesheets + inline styles. `href`s are resolved RELATIVE to the\n * html file's directory; remote/protocol-relative/`data:` hrefs and files that don't exist on disk are\n * dropped. Query/hash suffixes (`style.css?v=2`) are stripped before resolution.\n */\nexport function extractHtmlStylesheets(htmlCode: string, htmlAbsPath: string): HtmlStylesheets {\n const dir = path.dirname(path.resolve(htmlAbsPath));\n const files: string[] = [];\n const seen = new Set<string>();\n for (const rawHref of extractLinkHrefs(htmlCode)) {\n const href = (rawHref.split(/[?#]/, 1)[0] ?? '').trim();\n if (!href || isRemoteHref(href)) continue;\n const abs = path.resolve(dir, href);\n if (seen.has(abs)) continue;\n seen.add(abs);\n if (existsSync(abs)) files.push(abs);\n }\n return { files, inline: extractInlineStyles(htmlCode) };\n}\n\n/**\n * A stable cache key for a CSS set: the sorted, resolved stylesheet paths plus a hash of the inline\n * blocks. Two HTML files that pull in the same imports (and inline the same styles) produce the same\n * key, so the transform reuses one resolver for both — hundreds of pages stay fast.\n */\nexport function cssSetKey(sortedResolvedPaths: readonly string[], inline: readonly string[]): string {\n const h = createHash('sha1');\n h.update(sortedResolvedPaths.join('\\n'));\n h.update('\u0000inline\u0000');\n for (const block of inline) {\n h.update(block);\n h.update('\u0000');\n }\n return h.digest('hex');\n}\n"],"mappings":"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","names":["path","path"]}
|