@wix/origami-transforms 0.0.0-stage → 0.1.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.
Files changed (74) hide show
  1. package/README.md +15 -2
  2. package/dist/cssPlugins/customMedia.d.ts +2 -0
  3. package/dist/cssPlugins/fonts.d.ts +2 -0
  4. package/dist/cssPlugins/index.d.ts +2 -0
  5. package/dist/cssPlugins/layout/compiler.d.ts +1 -0
  6. package/dist/cssPlugins/layout/contract.d.ts +12 -0
  7. package/dist/cssPlugins/layout/index.d.ts +2 -0
  8. package/dist/cssPlugins/layout/parser.d.ts +2 -0
  9. package/dist/cssPlugins/mixins.d.ts +2 -0
  10. package/dist/cssPlugins/presets.d.ts +2 -0
  11. package/dist/cssPlugins/spx/calls.d.ts +1 -0
  12. package/dist/cssPlugins/spx/cascade.d.ts +1 -0
  13. package/dist/cssPlugins/spx/index.d.ts +2 -0
  14. package/dist/cssPlugins/stackChildren.d.ts +2 -0
  15. package/dist/index.d.ts +12 -0
  16. package/dist/jsxPlugins/index.d.ts +2 -0
  17. package/dist/jsxPlugins/mediaValidation/index.d.ts +2 -0
  18. package/dist/jsxPlugins/origamiCompatibility/ast.d.ts +3 -0
  19. package/dist/jsxPlugins/origamiCompatibility/componentBindings.d.ts +1 -0
  20. package/dist/jsxPlugins/origamiCompatibility/index.d.ts +2 -0
  21. package/dist/jsxPlugins/origamiCompatibility/rules/containerProps.d.ts +4 -0
  22. package/dist/jsxPlugins/origamiCompatibility/rules/richTextHtml.d.ts +4 -0
  23. package/dist/jsxPlugins/validation/checks.d.ts +12 -0
  24. package/dist/jsxPlugins/validation/index.d.ts +2 -0
  25. package/dist/jsxPlugins/validation/rules.d.ts +7 -0
  26. package/dist/jsxPlugins/validation/tree.d.ts +1 -0
  27. package/dist/transform/context.d.ts +13 -0
  28. package/dist/transform/css/postcssPlugin.d.ts +10 -0
  29. package/dist/transform/css/runPlugins.d.ts +5 -0
  30. package/dist/transform/jsx/babelAdapter.d.ts +19 -0
  31. package/dist/transform/jsx/runPlugins.d.ts +5 -0
  32. package/dist/transform/layoutError.d.ts +19 -0
  33. package/dist/transform/pipeline.d.ts +31 -0
  34. package/dist/transform/registry.d.ts +5 -0
  35. package/dist/transform/relativeToSite.d.ts +1 -0
  36. package/dist/transform/types.d.ts +86 -0
  37. package/package.json +55 -3
  38. package/src/cssPlugins/customMedia.js +52 -0
  39. package/src/cssPlugins/fonts.js +93 -0
  40. package/src/cssPlugins/index.js +32 -0
  41. package/src/cssPlugins/layout/README.md +77 -0
  42. package/src/cssPlugins/layout/compiler.js +116 -0
  43. package/src/cssPlugins/layout/contract.js +35 -0
  44. package/src/cssPlugins/layout/index.js +12 -0
  45. package/src/cssPlugins/layout/parser.js +81 -0
  46. package/src/cssPlugins/mixins.js +63 -0
  47. package/src/cssPlugins/presets.js +73 -0
  48. package/src/cssPlugins/spx/README.md +73 -0
  49. package/src/cssPlugins/spx/calls.js +12 -0
  50. package/src/cssPlugins/spx/cascade.js +41 -0
  51. package/src/cssPlugins/spx/index.js +13 -0
  52. package/src/cssPlugins/stackChildren.js +205 -0
  53. package/src/index.js +12 -0
  54. package/src/jsxPlugins/index.js +10 -0
  55. package/src/jsxPlugins/mediaValidation/index.js +49 -0
  56. package/src/jsxPlugins/origamiCompatibility/ast.js +30 -0
  57. package/src/jsxPlugins/origamiCompatibility/componentBindings.js +40 -0
  58. package/src/jsxPlugins/origamiCompatibility/index.js +48 -0
  59. package/src/jsxPlugins/origamiCompatibility/rules/containerProps.js +15 -0
  60. package/src/jsxPlugins/origamiCompatibility/rules/richTextHtml.js +22 -0
  61. package/src/jsxPlugins/validation/checks.js +105 -0
  62. package/src/jsxPlugins/validation/index.js +65 -0
  63. package/src/jsxPlugins/validation/rules.js +24 -0
  64. package/src/jsxPlugins/validation/tree.js +15 -0
  65. package/src/transform/context.js +13 -0
  66. package/src/transform/css/postcssPlugin.js +60 -0
  67. package/src/transform/css/runPlugins.js +40 -0
  68. package/src/transform/jsx/babelAdapter.js +37 -0
  69. package/src/transform/jsx/runPlugins.js +23 -0
  70. package/src/transform/layoutError.js +19 -0
  71. package/src/transform/pipeline.js +60 -0
  72. package/src/transform/registry.js +40 -0
  73. package/src/transform/relativeToSite.js +9 -0
  74. package/src/transform/types.js +52 -0
package/README.md CHANGED
@@ -1,3 +1,16 @@
1
- # Temporary Holding Version
1
+ # @wix/origami-transforms
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Babel and PostCSS checks and transforms for Wix origami page files.
4
+
5
+ Exports `createTransformPipeline`, `createTransformRegistry`, `createContextFactory`,
6
+ `relativeToSite`, `LayoutError`, the Babel/PostCSS adapters and runners, `jsxPlugins`
7
+ and `cssPlugins`. The pipeline accepts the existing `{ layout, jsxPlugins, cssPlugins }`
8
+ options, exposes `babelOptionsFor` and `postcssPlugin`, and takes `serve` or `build`
9
+ through `setCommand`. It has no Vite dependency.
10
+
11
+ JavaScript source lives in `src/`; `pnpm build` emits declarations to `dist/`.
12
+ Run `pnpm test` and `pnpm lint` for the transform suite and lint checks.
13
+ Transform specs and CSS fixtures live only in this package. The old runtime keeps its
14
+ own integration, browser and packed-package tests. Consumers import the shared API
15
+ and types from `@wix/origami-transforms`; there are no public subpath exports.
16
+ The host layout only needs a `root` string.
@@ -0,0 +1,2 @@
1
+ /** @type {import('../transform/types.js').CssTransformPlugin} */
2
+ export const customMediaPlugin: import("../transform/types.js").CssTransformPlugin;
@@ -0,0 +1,2 @@
1
+ /** @type {import('../transform/types.js').CssTransformPlugin} */
2
+ export const fontsPlugin: import("../transform/types.js").CssTransformPlugin;
@@ -0,0 +1,2 @@
1
+ /** @type {readonly import('../transform/types.js').CssTransformPlugin[]} */
2
+ export const cssPlugins: readonly import("../transform/types.js").CssTransformPlugin[];
@@ -0,0 +1 @@
1
+ export function compileContainers(root: import("postcss").Root, context: import("../../transform/types.js").TransformContext): void;
@@ -0,0 +1,12 @@
1
+ export const CONTENT: "internal-container-content-wrapper";
2
+ export const OVERFLOW: "internal-container-overflow-wrapper";
3
+ export const targets: Readonly<{
4
+ "internal-container-content-wrapper": Set<string>;
5
+ "internal-container-overflow-wrapper": Set<string>;
6
+ }>;
7
+ export const displays: Readonly<{
8
+ GridContainerLayout: "grid";
9
+ FlexContainerLayout: "flex";
10
+ StackContainerLayout: "flex";
11
+ OrganizerContainerLayout: "flex";
12
+ }>;
@@ -0,0 +1,2 @@
1
+ /** @type {import('../../transform/types.js').CssTransformPlugin} */
2
+ export const layoutPlugin: import("../../transform/types.js").CssTransformPlugin;
@@ -0,0 +1,2 @@
1
+ export function isLayoutSource(node: import("postcss").Node, context: import("../../transform/types.js").TransformContext): boolean;
2
+ export function parseTargets(root: import("postcss").Root, context: import("../../transform/types.js").TransformContext): Map<import("postcss").Rule, import("postcss").AtRule[]>;
@@ -0,0 +1,2 @@
1
+ /** @type {import('../transform/types.js').CssTransformPlugin} */
2
+ export const mixinsPlugin: import("../transform/types.js").CssTransformPlugin;
@@ -0,0 +1,2 @@
1
+ /** @type {import('../transform/types.js').CssTransformPlugin} */
2
+ export const presetsPlugin: import("../transform/types.js").CssTransformPlugin;
@@ -0,0 +1 @@
1
+ export function lowerCalls(declaration: import("postcss").Declaration, resolve: (value: number) => string, invalidCall: () => Error): void;
@@ -0,0 +1 @@
1
+ export function compileCascadeDeclaration(declaration: import("postcss").Declaration): void;
@@ -0,0 +1,2 @@
1
+ /** @type {import('../../transform/types.js').CssTransformPlugin} */
2
+ export const spxPlugin: import("../../transform/types.js").CssTransformPlugin;
@@ -0,0 +1,2 @@
1
+ /** @type {import('../transform/types.js').CssTransformPlugin} */
2
+ export const stackChildrenPlugin: import("../transform/types.js").CssTransformPlugin;
@@ -0,0 +1,12 @@
1
+ export * from "./transform/pipeline.js";
2
+ export * from "./transform/registry.js";
3
+ export * from "./transform/context.js";
4
+ export * from "./transform/relativeToSite.js";
5
+ export * from "./transform/layoutError.js";
6
+ export * from "./transform/jsx/babelAdapter.js";
7
+ export * from "./transform/jsx/runPlugins.js";
8
+ export * from "./transform/css/postcssPlugin.js";
9
+ export * from "./transform/css/runPlugins.js";
10
+ export * from "./jsxPlugins/index.js";
11
+ export * from "./cssPlugins/index.js";
12
+ export * from "./transform/types.js";
@@ -0,0 +1,2 @@
1
+ /** @type {readonly import('../transform/types.js').JsxTransformPlugin[]} */
2
+ export const jsxPlugins: readonly import("../transform/types.js").JsxTransformPlugin[];
@@ -0,0 +1,2 @@
1
+ /** @type {import('../../transform/types.js').JsxTransformPlugin} */
2
+ export const mediaValidationPlugin: import("../../transform/types.js").JsxTransformPlugin;
@@ -0,0 +1,3 @@
1
+ export function findAttribute(attributes: any, name: any): any;
2
+ export function indexProperties(properties: any): Map<any, any>;
3
+ export function objectPropValue(openingElement: any, name: any): any;
@@ -0,0 +1 @@
1
+ export function collectComponentBindings(program: object, rulesByComponent: Map<string, readonly object[]>): Map<string, readonly object[]>;
@@ -0,0 +1,2 @@
1
+ /** @type {import('../../transform/types.js').JsxTransformPlugin} */
2
+ export const origamiCompatibilityPlugin: import("../../transform/types.js").JsxTransformPlugin;
@@ -0,0 +1,4 @@
1
+ export namespace containerPropsRule {
2
+ let components: string[];
3
+ function apply(openingElement: any): void;
4
+ }
@@ -0,0 +1,4 @@
1
+ export namespace richTextHtmlRule {
2
+ let components: string[];
3
+ function apply(openingElement: any): void;
4
+ }
@@ -0,0 +1,12 @@
1
+ export function assertKnownConstraints(rules: any): void;
2
+ export function constrainsChildren(rule: any): boolean;
3
+ export function checkRule(rule: any, { node, componentOf, report }: {
4
+ node: any;
5
+ componentOf: any;
6
+ report: any;
7
+ }): void;
8
+ export function checkCreateElement(rule: any, { node, component, report }: {
9
+ node: any;
10
+ component: any;
11
+ report: any;
12
+ }): any;
@@ -0,0 +1,2 @@
1
+ /** @type {import('../../transform/types.js').JsxTransformPlugin} */
2
+ export const layoutValidationPlugin: import("../../transform/types.js").JsxTransformPlugin;
@@ -0,0 +1,7 @@
1
+ export const RULES: {
2
+ id: string;
3
+ components: string[];
4
+ allowedChildren: string[];
5
+ minChildren: number;
6
+ maxChildren: number;
7
+ }[];
@@ -0,0 +1 @@
1
+ export function describe(node: any): string;
@@ -0,0 +1,13 @@
1
+ export function createContextFactory({ layout, command }: {
2
+ layout: any;
3
+ command: any;
4
+ }): (id: any, { ssr }?: {
5
+ ssr?: boolean | undefined;
6
+ }) => Readonly<{
7
+ id: any;
8
+ filePath: any;
9
+ relativePath: string | undefined;
10
+ layout: any;
11
+ command: any;
12
+ ssr: boolean;
13
+ }>;
@@ -0,0 +1,10 @@
1
+ export function createCssPostcssPlugin({ selectPlugins, contextFor, failures }: {
2
+ selectPlugins: (context: import("../types.js").TransformContext) => readonly object[];
3
+ contextFor: (id: string) => import("../types.js").TransformContext;
4
+ failures?: Map<string, Set<string>>;
5
+ }): {
6
+ postcssPlugin: string;
7
+ Once(root: any, { result }: {
8
+ result: any;
9
+ }): void;
10
+ };
@@ -0,0 +1,5 @@
1
+ export function runCssPlugins({ plugins, root, context }: {
2
+ plugins: any;
3
+ root: any;
4
+ context: any;
5
+ }): void;
@@ -0,0 +1,19 @@
1
+ export function createBabelOptionsSelector({ selectPlugins, contextFor }: {
2
+ selectPlugins: any;
3
+ contextFor: any;
4
+ }): (id: any, { ssr }?: {
5
+ ssr?: boolean | undefined;
6
+ }) => {
7
+ parserOpts?: {
8
+ plugins: any[];
9
+ };
10
+ plugins: ((() => {
11
+ name: string;
12
+ visitor: {
13
+ Program(program: any, state: any): void;
14
+ };
15
+ }) | {
16
+ plugins: any;
17
+ context: any;
18
+ })[][];
19
+ };
@@ -0,0 +1,5 @@
1
+ export function runJsxPlugins({ plugins, program, context }: {
2
+ plugins: any;
3
+ program: any;
4
+ context: any;
5
+ }): void;
@@ -0,0 +1,19 @@
1
+ export class LayoutError extends Error {
2
+ /**
3
+ * @param {readonly string[]} violations
4
+ * @param {{ file?: string, line?: number, column?: number }} [location] The first violation's, which the
5
+ * bundler prints as the error's position (Vite's CSS stage reads `file`, `line` and `column`).
6
+ */
7
+ constructor(violations: readonly string[], location?: {
8
+ file?: string;
9
+ line?: number;
10
+ column?: number;
11
+ });
12
+ stack: string;
13
+ violations: readonly string[];
14
+ location: {
15
+ file?: string;
16
+ line?: number;
17
+ column?: number;
18
+ };
19
+ }
@@ -0,0 +1,31 @@
1
+ export function createTransformPipeline({ layout, jsxPlugins, cssPlugins }: {
2
+ layout: import("./types.js").SiteLayout;
3
+ jsxPlugins?: readonly import("./types.js").JsxTransformPlugin[];
4
+ cssPlugins?: readonly import("./types.js").CssTransformPlugin[];
5
+ }): {
6
+ postcssPlugin: {
7
+ postcssPlugin: string;
8
+ Once(root: any, { result }: {
9
+ result: any;
10
+ }): void;
11
+ };
12
+ babelOptionsFor: (id: any, { ssr }?: {
13
+ ssr?: boolean | undefined;
14
+ }) => {
15
+ parserOpts?: {
16
+ plugins: any[];
17
+ };
18
+ plugins: ((() => {
19
+ name: string;
20
+ visitor: {
21
+ Program(program: any, state: any): void;
22
+ };
23
+ }) | {
24
+ plugins: any;
25
+ context: any;
26
+ })[][];
27
+ };
28
+ /** Whether a stylesheet whose last CSS run failed declared `file`, so an edit to it may fix the stylesheet. */
29
+ failedStylesheetDependsOn: (file: any) => boolean;
30
+ setCommand: (value: any) => void;
31
+ };
@@ -0,0 +1,5 @@
1
+ export function createTransformRegistry(plugins?: readonly any[]): {
2
+ register: (plugin: any) => /*elided*/ any;
3
+ /** The plugins that apply to `context`, in order. Frozen; safe to hold onto. */
4
+ select: (context: any) => readonly any[];
5
+ };
@@ -0,0 +1 @@
1
+ export function relativeToSite(root: any, id: any): string | undefined;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The site root supplied by the host runtime.
3
+ */
4
+ export type SiteLayout = {
5
+ root: string;
6
+ };
7
+ /**
8
+ * The transform-plugin contract.
9
+ *
10
+ * Both stages have the same shape: an engine parses the file once, and each plugin is handed that
11
+ * AST plus the file's context and does what it likes with it. Babel and PostCSS are engines here,
12
+ * not plugin hosts — a plugin never has to know either one's own plugin protocol. Which stage a
13
+ * plugin belongs to is decided by the list it is registered in, so no descriptor restates it.
14
+ */
15
+ export type TransformContext = {
16
+ /**
17
+ * Module id as the bundler reports it, query string included.
18
+ */
19
+ id: string;
20
+ /**
21
+ * `id` with any query stripped.
22
+ */
23
+ filePath: string;
24
+ /**
25
+ * Posix path relative to the site root, or undefined when the file lies outside it.
26
+ */
27
+ relativePath: string | undefined;
28
+ layout: SiteLayout;
29
+ command: "serve" | "build";
30
+ /**
31
+ * Always false for the CSS stage, which PostCSS drives without an SSR signal.
32
+ */
33
+ ssr: boolean;
34
+ };
35
+ /**
36
+ * The transform-plugin contract.
37
+ *
38
+ * Both stages have the same shape: an engine parses the file once, and each plugin is handed that
39
+ * AST plus the file's context and does what it likes with it. Babel and PostCSS are engines here,
40
+ * not plugin hosts — a plugin never has to know either one's own plugin protocol. Which stage a
41
+ * plugin belongs to is decided by the list it is registered in, so no descriptor restates it.
42
+ */
43
+ export type TransformPluginBase = {
44
+ name: string;
45
+ /**
46
+ * Ascending. Ties break by registration order.
47
+ */
48
+ order: number;
49
+ /**
50
+ * Whether the plugin runs for this
51
+ * file. Must depend only on the file's identity, not on anything that can change between transforms
52
+ * of the same file: the pipeline memoizes the selection per file path.
53
+ */
54
+ appliesTo?: (context: TransformContext) => boolean;
55
+ };
56
+ /**
57
+ * `program` is the Babel `NodePath` for the module's Program node. Traverse it if you need to —
58
+ * nothing walks it for you, so a plugin that can rule itself out cheaply should just return.
59
+ * `parserPlugins` are Babel parser plugins the file needs; they are unioned across selected plugins
60
+ * and applied before any of them run.
61
+ */
62
+ export type JsxTransformPlugin = TransformPluginBase & {
63
+ transform: (program: any, context: TransformContext) => void;
64
+ parserPlugins?: readonly string[];
65
+ };
66
+ /**
67
+ * The transform-plugin contract.
68
+ *
69
+ * Both stages have the same shape: an engine parses the file once, and each plugin is handed that
70
+ * AST plus the file's context and does what it likes with it. Babel and PostCSS are engines here,
71
+ * not plugin hosts — a plugin never has to know either one's own plugin protocol. Which stage a
72
+ * plugin belongs to is decided by the list it is registered in, so no descriptor restates it.
73
+ */
74
+ export type CssTransformPlugin = TransformPluginBase & {
75
+ validate?: (root: import("postcss").Root, context: CssTransformContext) => void;
76
+ transform: (root: import("postcss").Root, context: CssTransformContext) => void;
77
+ };
78
+ /**
79
+ * `addDependency` declares a file the plugin read besides the stylesheet, so the bundler re-runs the
80
+ * stylesheet when it changes. `warn` reports a problem at `node` without failing the run, as a PostCSS
81
+ * warning on the stylesheet's result. Both are absent when no bundler drives the run.
82
+ */
83
+ export type CssTransformContext = TransformContext & {
84
+ addDependency?: (file: string) => void;
85
+ warn?: (node: import("postcss").Node, text: string) => void;
86
+ };
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@wix/origami-transforms",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Babel and PostCSS checks and transforms for Wix origami page files.",
5
+ "main": "./src/index.js",
6
+ "files": [
7
+ "src",
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -b .",
12
+ "lint": "oxlint .",
13
+ "test": "node --test test/*.spec.js",
14
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
15
+ },
16
+ "dependencies": {
17
+ "@babel/core": "^7.26.0",
18
+ "@wix/origami-fonts": "1.0.0",
19
+ "@wix/origami-system-components": "1.6.0",
20
+ "postcss": "^8.5.3",
21
+ "postcss-custom-media": "^12.0.1"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "typescript": "^6.0.3"
26
+ },
27
+ "publishUnscoped": false,
28
+ "publishConfig": {
29
+ "registry": "https://registry.npmjs.org/",
30
+ "access": "public"
31
+ },
32
+ "wix": {
33
+ "artifact": {
34
+ "groupId": "com.wixpress.npm",
35
+ "artifactId": "origami-transforms",
36
+ "targets": {
37
+ "docker": false
38
+ }
39
+ },
40
+ "validations": {
41
+ "source": [
42
+ "lint"
43
+ ],
44
+ "postBuild": [
45
+ "test"
46
+ ]
47
+ }
48
+ },
49
+ "exports": {
50
+ ".": {
51
+ "types": "./dist/index.d.ts",
52
+ "default": "./src/index.js"
53
+ }
54
+ },
55
+ "type": "module",
56
+ "types": "./dist/index.d.ts",
57
+ "falconPackageHash": "6410a8c82ddf594a8cf6d774ed79445c9589127257b0fc5c50834dfd"
6
58
  }
@@ -0,0 +1,52 @@
1
+ // `@custom-media --name (query);` with `@media (--name) {}` (Media Queries Level 5). No browser ships it, so the
2
+ // alias is always expanded. The expansion is postcss-custom-media's, which matches the DM output exactly; this
3
+ // descriptor only runs that plugin over the stage's shared Root, so the stage keeps its gating (site-owned files)
4
+ // and its ordering.
5
+ //
6
+ // A definition is only visible within the document that declares it. DM emits the definitions in the theme
7
+ // carrier, the generator `@import`s that into every page stylesheet, and Vite's postcss-import inlines it before
8
+ // this stage runs — so definition and usage share one Root.
9
+
10
+ import postcss from 'postcss'
11
+ import postcssCustomMedia from 'postcss-custom-media'
12
+
13
+ const processor = postcss([postcssCustomMedia()])
14
+
15
+ const CUSTOM_MEDIA_QUERY = /\(\s*--/
16
+
17
+ // Whether the document uses the feature at all: a definition to remove, or an alias to expand. Every carrier but
18
+ // the theme has neither, and this walk visits only at-rules, where the expansion walks and re-marks every node.
19
+ const usesCustomMedia = root => {
20
+ let used = false
21
+ root.walkAtRules(atRule => {
22
+ if (atRule.name === 'custom-media' || (atRule.name === 'media' && CUSTOM_MEDIA_QUERY.test(atRule.params))) {
23
+ used = true
24
+ return false
25
+ }
26
+ })
27
+ return used
28
+ }
29
+
30
+ /** @type {import('../transform/types.js').CssTransformPlugin} */
31
+ export const customMediaPlugin = {
32
+ name: 'custom-media',
33
+ order: 0,
34
+ transform(root, context) {
35
+ if (!usesCustomMedia(root)) return
36
+ // A Root handed to `process` is transformed in place. The plugin has no async hooks, so `sync` is safe.
37
+ // Its warnings go to the nested Result and are dropped: the only one it raises is a cyclic definition,
38
+ // which leaves the alias unexpanded and so fails below anyway. A definition it cannot parse at all is a
39
+ // different matter — it emits the unparsed bytes as the query and warns about nothing, which no check
40
+ // here can see. DM does not emit one.
41
+ processor.process(root, { from: context.filePath }).sync()
42
+ // An unexpanded alias is left as authored, and a browser evaluates it to "unknown", which silently
43
+ // disables the whole block.
44
+ root.walkAtRules('media', atRule => {
45
+ if (CUSTOM_MEDIA_QUERY.test(atRule.params)) {
46
+ throw new Error(
47
+ `@media ${atRule.params} has no usable @custom-media definition: it is missing or cyclic`
48
+ )
49
+ }
50
+ })
51
+ },
52
+ }
@@ -0,0 +1,93 @@
1
+ // Checks the theme's font tokens (`--wst-heading-1-font: normal normal 700 --spx(88)/1em benzin`). It changes
2
+ // nothing. The editor reads the family at the end of each one as one Wix font id and fails the import otherwise, so
3
+ // the build fails, with every such token listed, unless the family is exactly the id of a font in the Wix font
4
+ // catalog bundled in @wix/origami-fonts: no quotes, commas or fallbacks, the same case. A legacy font, which the
5
+ // editor no longer offers, counts as not in the catalog. `origami search-font` finds the right id.
6
+
7
+ import { basename } from 'node:path'
8
+ import { fonts } from '@wix/origami-fonts'
9
+ import { LayoutError } from '../transform/layoutError.js'
10
+
11
+ const FONT_TOKEN = /^--wst-[\w-]+-font$/
12
+ // The size in a `font` shorthand, with its optional line height: the family follows it. The number's quantifiers
13
+ // never overlap, so a long run of digits that is not a size fails in linear time.
14
+ const SIZE = /^(?:--spx\(.*\)|calc\(.*\)|(?:\d+(?:\.\d*)?|\.\d+)(?:px|em|rem|%|vw|vh|pt)?)(?:\/\S+)?$/i
15
+ const FONT_IDS = new Set(fonts.filter(font => font.permissions !== 'legacy').map(font => font.id))
16
+
17
+ /** A value split on top-level whitespace, so `--spx(16)/1.3em` stays one token. */
18
+ const tokens = value => {
19
+ const result = []
20
+ let depth = 0
21
+ let current = ''
22
+ for (const char of value.trim()) {
23
+ if (char === '(') depth++
24
+ else if (char === ')') depth--
25
+ if (depth === 0 && /\s/.test(char)) {
26
+ if (current) result.push(current)
27
+ current = ''
28
+ } else current += char
29
+ }
30
+ if (current) result.push(current)
31
+ return result
32
+ }
33
+
34
+ /** A font shorthand's family as written; undefined for `var(…)`. */
35
+ const familyOf = value => {
36
+ if (/var\(/.test(value)) return undefined
37
+ const parts = tokens(value)
38
+ // The last size: the repo's TS lib (ES2022) has no findLastIndex.
39
+ let size = parts.length - 1
40
+ while (size >= 0 && !SIZE.test(parts[size])) size--
41
+ if (size === -1 || size === parts.length - 1) return undefined
42
+ return parts.slice(size + 1).join(' ')
43
+ }
44
+
45
+ /** ` (theme.css:12)`: the carrier the declaration was written in, which the bundler doesn't name. */
46
+ const where = declaration => {
47
+ const file = declaration.source?.input.file
48
+ const line = declaration.source?.start?.line
49
+ return file ? ` (${basename(file)}${line ? `:${line}` : ''})` : ''
50
+ }
51
+
52
+ /**
53
+ * Where `source` starts, as LayoutError's location, leaving out what it doesn't know.
54
+ * @param {import('postcss').Declaration['source']} source
55
+ */
56
+ const locationOf = source => {
57
+ /** @type {{ file?: string, line?: number, column?: number }} */
58
+ const location = {}
59
+ if (source?.input.file !== undefined) location.file = source.input.file
60
+ if (source?.start !== undefined) {
61
+ location.line = source.start.line
62
+ location.column = source.start.column
63
+ }
64
+ return location
65
+ }
66
+
67
+ /** @type {import('../transform/types.js').CssTransformPlugin} */
68
+ export const fontsPlugin = {
69
+ name: 'fonts',
70
+ // Only checks; it reads the tokens as authored, before spx rewrites the sizes the family follows.
71
+ order: 5,
72
+ transform(root) {
73
+ /** @type {import('postcss').Declaration[]} */
74
+ const declarations = []
75
+ root.walkDecls(FONT_TOKEN, declaration => {
76
+ declarations.push(declaration)
77
+ })
78
+ const violations = []
79
+ let first
80
+ for (const declaration of declarations) {
81
+ const family = familyOf(declaration.value)
82
+ if (family === undefined || FONT_IDS.has(family)) continue
83
+ first ??= declaration.source
84
+ violations.push(
85
+ `${declaration.prop}${where(declaration)} must name exactly one Wix font id from the catalog; found ` +
86
+ `${family}. Run \`origami search-font --font <name>\` for the id.`
87
+ )
88
+ }
89
+ if (violations.length) {
90
+ throw new LayoutError(violations, locationOf(first))
91
+ }
92
+ },
93
+ }
@@ -0,0 +1,32 @@
1
+ // The CSS stage's plugins, in the order they run. DM authors its CSS in features no browser implements yet, or only
2
+ // Chromium does; these compile them away so a generated site renders everywhere. A caller can still pass its own
3
+ // list through `createSiteViteConfig({ cssPlugins })`.
4
+ //
5
+ // A descriptor needs a `name`, an `order`, and `transform(root, context)`, where `root` is the PostCSS Root for the
6
+ // stylesheet; see `../transform/types.js` for the contract and `../transform/css/postcssPlugin.js` for what the
7
+ // stage guarantees a plugin sees. The order matters once: mixin bodies are copied into their calling rules before
8
+ // layout routes declarations to wrappers and SPX converts calls using the browser's active settings. stack-children
9
+ // only checks, and runs between mixins and layout: it needs the mixins' declarations and the stack rules as authored,
10
+ // before layout compiles them away. presets runs after mixins too, so a `--preset` a mixin body carries is moved
11
+ // like an authored one. fonts only checks the theme's font tokens, as authored.
12
+ //
13
+ // `@layer` needs no plugin: cascade layers have been in every browser since March 2022.
14
+
15
+ import { customMediaPlugin } from './customMedia.js'
16
+ import { fontsPlugin } from './fonts.js'
17
+ import { mixinsPlugin } from './mixins.js'
18
+ import { presetsPlugin } from './presets.js'
19
+ import { layoutPlugin } from './layout/index.js'
20
+ import { spxPlugin } from './spx/index.js'
21
+ import { stackChildrenPlugin } from './stackChildren.js'
22
+
23
+ /** @type {readonly import('../transform/types.js').CssTransformPlugin[]} */
24
+ export const cssPlugins = Object.freeze([
25
+ customMediaPlugin,
26
+ fontsPlugin,
27
+ mixinsPlugin,
28
+ stackChildrenPlugin,
29
+ presetsPlugin,
30
+ layoutPlugin,
31
+ spxPlugin,
32
+ ])