@multiplatform.one/config 7.6.0 → 7.6.2
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 +8 -1
- package/drawing-registry.mjs +145 -0
- package/lib/index.js +4 -3
- package/lib/{storybook-CrdwuS_Y.js → storybook-CSzjegfP.js} +2 -0
- package/lib/storybook.js +1 -1
- package/lib/tamaguiWorkspacePaths-ZIcqybtn.js +58 -0
- package/lib/{vite-C7884n3X.js → vite-W9hIeg2_.js} +2 -0
- package/lib/vite.js +1 -1
- package/lib/vitest.js +3 -1
- package/lint.d.ts +2 -0
- package/lint.mjs +129 -1
- package/oxlint.d.ts +1 -0
- package/oxlint.mjs +275 -7
- package/package.json +4 -2
- package/src/drawing-registry.spec.ts +69 -0
- package/src/index.ts +6 -0
- package/src/lint.spec.ts +122 -0
- package/src/oxlint.spec.ts +180 -1
- package/src/storybook.ts +3 -0
- package/src/tamaguiWorkspacePaths.spec.ts +113 -0
- package/src/tamaguiWorkspacePaths.ts +85 -0
- package/src/vite.ts +4 -0
- package/src/vitest.ts +4 -0
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/storybook.d.ts.map +1 -1
- package/types/tamaguiWorkspacePaths.d.ts +35 -0
- package/types/tamaguiWorkspacePaths.d.ts.map +1 -0
- package/types/vite.d.ts.map +1 -1
- package/types/vitest.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -68,7 +68,14 @@ Convention rules travel with the package. Consuming apps add one line:
|
|
|
68
68
|
|
|
69
69
|
Then enable `mpo-conventions/no-hex-literals` on the globs you want (mpo's
|
|
70
70
|
own `oxlintrc.json` turns it on for `features/**`). Palette modules
|
|
71
|
-
(`themes/base.ts`, `themes/accent.ts`, `*palette*`) are exempt.
|
|
71
|
+
(`themes/base.ts`, `themes/accent.ts`, `*palette*`) are exempt. Comments
|
|
72
|
+
are not visited, so a JSDoc `#025` is not a hit. Widening to `apps/**`
|
|
73
|
+
and `packages/**` is 182 hits on this tree (153 outside tests) — record
|
|
74
|
+
the count, then burn down; do not flip the glob until then.
|
|
75
|
+
|
|
76
|
+
`no-chrome-props` and `no-raw-geometry` ship in the same plugin (MPO-17).
|
|
77
|
+
Enable them the same way when you start that burndown. Drawing-file
|
|
78
|
+
exceptions come from the spec table `## Drawing files`, not from the code.
|
|
72
79
|
|
|
73
80
|
### Lint (node-script checks)
|
|
74
81
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drawing / chrome-allow registry — parsed from the living rulebook.
|
|
3
|
+
*
|
|
4
|
+
* MPO-17: raw numeric geometry is legal only in files listed as DRAWING
|
|
5
|
+
* members, and chrome props on imported @multiplatform.one/* components are
|
|
6
|
+
* legal only for CHROME-ALLOW members. Both lists are parsed from
|
|
7
|
+
* `docs/theme-propagation-spec.md` `## Drawing files`, the same shape
|
|
8
|
+
* scripts/radius-identity-registry.mjs uses for LC-60. Writing more
|
|
9
|
+
* violations into the code cannot widen either list.
|
|
10
|
+
*
|
|
11
|
+
* A consumer tree with no spec (or no Drawing files section) yields empty
|
|
12
|
+
* lists rather than throwing, so shc can pick the oxlint rules up before it
|
|
13
|
+
* has copied this table.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* import { readDrawingRegistry } from "./drawing-registry.mjs";
|
|
17
|
+
* node drawing-registry.mjs [--json]
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { pathToFileURL } from "node:url";
|
|
22
|
+
|
|
23
|
+
export const DRAWING_HEADING = "## Drawing files";
|
|
24
|
+
export const DRAWING_CLASS = "DRAWING";
|
|
25
|
+
export const CHROME_ALLOW_CLASS = "CHROME-ALLOW";
|
|
26
|
+
|
|
27
|
+
/** `// mpo-drawing` or `/* mpo-drawing` at the start of a line. */
|
|
28
|
+
export const DRAWING_PRAGMA = /^\s*(?:\/\/|\/\*)\s*mpo-drawing\b/m;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} [cwd]
|
|
32
|
+
*/
|
|
33
|
+
export function defaultSpecPath(cwd = process.cwd()) {
|
|
34
|
+
return join(cwd, "docs/theme-propagation-spec.md");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `Members: **Avatar**, **Radio disc**.` → ["Avatar", "Radio disc"]. */
|
|
38
|
+
function parseMembers(text) {
|
|
39
|
+
const match = text.match(/Members:\s*(.*)$/i);
|
|
40
|
+
if (!match) return [];
|
|
41
|
+
return [...match[1].matchAll(/\*\*([^*]+)\*\*/g)].map((m) => m[1].trim()).filter(Boolean);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} markdown
|
|
46
|
+
* @param {string} specPath
|
|
47
|
+
*/
|
|
48
|
+
function parseDrawingTable(markdown, specPath) {
|
|
49
|
+
const lines = markdown.split("\n");
|
|
50
|
+
const heading = lines.findIndex((line) => /^##\s+Drawing files\s*$/.test(line));
|
|
51
|
+
if (heading === -1) return null;
|
|
52
|
+
|
|
53
|
+
/** @type {{ id: string, text: string }[]} */
|
|
54
|
+
const rows = [];
|
|
55
|
+
let seenTable = false;
|
|
56
|
+
for (let i = heading + 1; i < lines.length; i++) {
|
|
57
|
+
const line = lines[i].trim();
|
|
58
|
+
if (line.startsWith("##")) break;
|
|
59
|
+
if (!line.startsWith("|")) {
|
|
60
|
+
if (seenTable) break;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
seenTable = true;
|
|
64
|
+
const cells = line
|
|
65
|
+
.split("|")
|
|
66
|
+
.slice(1, -1)
|
|
67
|
+
.map((c) => c.trim());
|
|
68
|
+
if (cells.length < 2) continue;
|
|
69
|
+
if (/^:?-{2,}/.test(cells[0])) continue;
|
|
70
|
+
if (cells[0].toLowerCase() === "class") continue;
|
|
71
|
+
rows.push({ id: cells[0], text: cells[1] });
|
|
72
|
+
}
|
|
73
|
+
if (rows.length === 0) {
|
|
74
|
+
throw new Error(`${specPath}: "${DRAWING_HEADING}" has no class rows`);
|
|
75
|
+
}
|
|
76
|
+
return rows;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {string} [specPath]
|
|
81
|
+
* @returns {{
|
|
82
|
+
* specPath: string,
|
|
83
|
+
* present: boolean,
|
|
84
|
+
* files: string[],
|
|
85
|
+
* chromeAllow: string[],
|
|
86
|
+
* }}
|
|
87
|
+
*/
|
|
88
|
+
export function readDrawingRegistry(specPath = defaultSpecPath()) {
|
|
89
|
+
if (!existsSync(specPath)) {
|
|
90
|
+
return { specPath, present: false, files: [], chromeAllow: [] };
|
|
91
|
+
}
|
|
92
|
+
const rows = parseDrawingTable(readFileSync(specPath, "utf8"), specPath);
|
|
93
|
+
if (!rows) {
|
|
94
|
+
return { specPath, present: false, files: [], chromeAllow: [] };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @type {Record<string, { text: string, members: string[] }>} */
|
|
98
|
+
const classes = {};
|
|
99
|
+
for (const row of rows) {
|
|
100
|
+
classes[row.id] = { text: row.text, members: parseMembers(row.text) };
|
|
101
|
+
}
|
|
102
|
+
if (!classes[DRAWING_CLASS]) {
|
|
103
|
+
throw new Error(`${specPath}: "${DRAWING_HEADING}" has no ${DRAWING_CLASS} row`);
|
|
104
|
+
}
|
|
105
|
+
if (!classes[CHROME_ALLOW_CLASS]) {
|
|
106
|
+
throw new Error(`${specPath}: "${DRAWING_HEADING}" has no ${CHROME_ALLOW_CLASS} row`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
specPath,
|
|
111
|
+
present: true,
|
|
112
|
+
files: classes[DRAWING_CLASS].members,
|
|
113
|
+
chromeAllow: classes[CHROME_ALLOW_CLASS].members,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* True when `filename` is one of the registered drawing files.
|
|
119
|
+
* Members are path suffixes (`listing/glyphs/CanvasGlyph.tsx`) or basenames.
|
|
120
|
+
*
|
|
121
|
+
* @param {string | undefined} filename
|
|
122
|
+
* @param {string[]} files
|
|
123
|
+
*/
|
|
124
|
+
export function isDrawingFile(filename, files) {
|
|
125
|
+
if (!filename || !files.length) return false;
|
|
126
|
+
const normalized = filename.replaceAll("\\", "/");
|
|
127
|
+
return files.some((entry) => {
|
|
128
|
+
const needle = entry.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
129
|
+
return normalized === needle || normalized.endsWith(`/${needle}`);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const invokedDirectly =
|
|
134
|
+
typeof process.argv[1] === "string" && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
135
|
+
|
|
136
|
+
if (invokedDirectly) {
|
|
137
|
+
const registry = readDrawingRegistry();
|
|
138
|
+
if (process.argv.includes("--json")) {
|
|
139
|
+
console.log(JSON.stringify(registry, null, 2));
|
|
140
|
+
} else {
|
|
141
|
+
console.log(`drawing registry from ${registry.specPath} (present=${registry.present})`);
|
|
142
|
+
console.log(` DRAWING: ${registry.files.join(", ") || "(none)"}`);
|
|
143
|
+
console.log(` CHROME-ALLOW: ${registry.chromeAllow.join(", ") || "(none)"}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { t as createViteConfig } from "./vite-
|
|
2
|
-
import { t as createStorybookViteConfig } from "./storybook-
|
|
1
|
+
import { t as createViteConfig } from "./vite-W9hIeg2_.js";
|
|
2
|
+
import { t as createStorybookViteConfig } from "./storybook-CSzjegfP.js";
|
|
3
|
+
import { n as tamaguiWorkspacePathsFile, t as ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths-ZIcqybtn.js";
|
|
3
4
|
import { createVitestConfig } from "./vitest.js";
|
|
4
5
|
|
|
5
|
-
export { createStorybookViteConfig, createViteConfig, createVitestConfig };
|
|
6
|
+
export { createStorybookViteConfig, createViteConfig, createVitestConfig, ensureTamaguiWorkspacePaths, tamaguiWorkspacePathsFile };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { r as resolvePackageMainSource, t as discoverPublicPackageRoots } from "./workspacePublicPackages-COicQSj4.js";
|
|
2
|
+
import { t as ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths-ZIcqybtn.js";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
@@ -89,6 +90,7 @@ function resolvePackageDir(pkgName, root, requireFrom) {
|
|
|
89
90
|
* ```
|
|
90
91
|
*/
|
|
91
92
|
function createStorybookViteConfig(options = {}) {
|
|
93
|
+
ensureTamaguiWorkspacePaths();
|
|
92
94
|
const workspaceRoot = options.workspaceRoot || findWorkspaceRoot();
|
|
93
95
|
const aliases = {
|
|
94
96
|
...discoverPackageAliases(path.join(workspaceRoot, "packages")),
|
package/lib/storybook.js
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
//#region src/tamaguiWorkspacePaths.ts
|
|
7
|
+
const tamaguiWorkspacePathsFile = "tsconfig/tamagui-workspace-paths.generated.json";
|
|
8
|
+
const generatorFile = "../../scripts/generate-tamagui-workspace-paths.mjs";
|
|
9
|
+
function thisPackageDir() {
|
|
10
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
}
|
|
12
|
+
function ensureTamaguiWorkspacePaths(options = {}) {
|
|
13
|
+
const { configPackageDir = thisPackageDir(), log = console.warn } = options;
|
|
14
|
+
const generatedPath = path.resolve(configPackageDir, tamaguiWorkspacePathsFile);
|
|
15
|
+
const generatorPath = path.resolve(configPackageDir, generatorFile);
|
|
16
|
+
if (fs.existsSync(generatedPath)) return {
|
|
17
|
+
generatedPath,
|
|
18
|
+
generatorPath,
|
|
19
|
+
status: "present"
|
|
20
|
+
};
|
|
21
|
+
if (!fs.existsSync(generatorPath)) return {
|
|
22
|
+
generatedPath,
|
|
23
|
+
generatorPath,
|
|
24
|
+
status: "no-generator"
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
execFileSync(process.execPath, [generatorPath], { stdio: [
|
|
28
|
+
"ignore",
|
|
29
|
+
"ignore",
|
|
30
|
+
"pipe"
|
|
31
|
+
] });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
+
log?.(`[@multiplatform.one/config] could not regenerate ${tamaguiWorkspacePathsFile}: ${message}`);
|
|
35
|
+
return {
|
|
36
|
+
generatedPath,
|
|
37
|
+
generatorPath,
|
|
38
|
+
status: "failed"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (!fs.existsSync(generatedPath)) {
|
|
42
|
+
log?.(`[@multiplatform.one/config] ${path.basename(generatorPath)} ran but did not write ${tamaguiWorkspacePathsFile}`);
|
|
43
|
+
return {
|
|
44
|
+
generatedPath,
|
|
45
|
+
generatorPath,
|
|
46
|
+
status: "failed"
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
log?.(`[@multiplatform.one/config] ${tamaguiWorkspacePathsFile} was missing (the root prepare script did not run; a global ignore-scripts skips it). Regenerated it. Run \`pnpm generate:tamagui-paths\` after install to skip this step.`);
|
|
50
|
+
return {
|
|
51
|
+
generatedPath,
|
|
52
|
+
generatorPath,
|
|
53
|
+
status: "generated"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { tamaguiWorkspacePathsFile as n, ensureTamaguiWorkspacePaths as t };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t as discoverPublicPackageRoots } from "./workspacePublicPackages-COicQSj4.js";
|
|
2
|
+
import { t as ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths-ZIcqybtn.js";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
@@ -77,6 +78,7 @@ function lookupProjectRoot() {
|
|
|
77
78
|
* ```
|
|
78
79
|
*/
|
|
79
80
|
function createViteConfig(options = {}) {
|
|
81
|
+
ensureTamaguiWorkspacePaths();
|
|
80
82
|
const { projectRoot: _projectRoot, publicConfigKeys = [], tamagui = true, i18n = false, one = false, ssr, server, plugins: extraPlugins = [], externalReactRouter, ssrDeps, ssrEntries, ssrCjsExternal, nativeFixes } = options;
|
|
81
83
|
const projectRoot = _projectRoot || findProjectRoot();
|
|
82
84
|
dotenv.config({ path: path.resolve(projectRoot, ".env") });
|
package/lib/vite.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createViteConfig } from "./vite-
|
|
1
|
+
import { t as createViteConfig } from "./vite-W9hIeg2_.js";
|
|
2
2
|
import { n as publicPackageViteSourceAliases, t as discoverPublicPackageRoots } from "./workspacePublicPackages-COicQSj4.js";
|
|
3
3
|
|
|
4
4
|
export { createViteConfig, discoverPublicPackageRoots, publicPackageViteSourceAliases };
|
package/lib/vitest.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { t as ensureTamaguiWorkspacePaths } from "./tamaguiWorkspacePaths-ZIcqybtn.js";
|
|
1
2
|
import Module from "node:module";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import { tamaguiPlugin } from "@tamagui/vite-plugin";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { tamaguiPlugin } from "@tamagui/vite-plugin";
|
|
6
7
|
import react from "@vitejs/plugin-react";
|
|
7
8
|
|
|
8
9
|
//#region src/vitest.ts
|
|
@@ -47,6 +48,7 @@ function pinNodeReactRequire(reactDir, reactDomDir, schedulerDir) {
|
|
|
47
48
|
* ```
|
|
48
49
|
*/
|
|
49
50
|
function createVitestConfig(options = {}) {
|
|
51
|
+
ensureTamaguiWorkspacePaths();
|
|
50
52
|
const { tamaguiConfig = "./tests/tamagui.config.ts", environment = "jsdom", setupFiles = [], testTimeout = 3e4, inlineDeps = [], externalDeps = [], aliases = {}, coverage, replaceReactNative = true, dedupeReact = true, rootNodeModules: _rootNodeModules, conditions = [
|
|
51
53
|
"source",
|
|
52
54
|
"test",
|
package/lint.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type ConventionRoots = {
|
|
|
3
3
|
apps?: string;
|
|
4
4
|
features?: string;
|
|
5
5
|
public?: string;
|
|
6
|
+
packages?: string;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
export type RunConventionChecksOptions = {
|
|
@@ -14,6 +15,7 @@ export function resolveRoots(roots?: ConventionRoots): {
|
|
|
14
15
|
apps: string;
|
|
15
16
|
features: string;
|
|
16
17
|
public: string;
|
|
18
|
+
packages: string;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export function runConventionChecks(options?: RunConventionChecksOptions): number;
|
package/lint.mjs
CHANGED
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
* `knobProps.control.height`) are fine. Escape with `sizeRecipeEscape` or
|
|
19
19
|
* `size-recipe-escape:` on the previous line or same statement. Pilot:
|
|
20
20
|
* Button/, InputParts/, fields/Select/ only.
|
|
21
|
+
* 5. Fail on a STORY FILE THE CSF INDEXER CANNOT READ (MPO-93) — a
|
|
22
|
+
* `*.stories.*` file that exports a named `meta` beside `export default`
|
|
23
|
+
* (`meta` is reserved for CSF Factories, so the indexer reports "missing
|
|
24
|
+
* default export"), or one with no default export at all. One such file
|
|
25
|
+
* kills the whole gallery and no package build ever sees it.
|
|
26
|
+
* 6. Fail on an UNDECLARED DRAWING FILE (MPO-17) — a `// mpo-drawing` pragma
|
|
27
|
+
* whose path is not a DRAWING member in docs/theme-propagation-spec.md.
|
|
28
|
+
* The oxlint rule skips registered drawings; this check is what makes
|
|
29
|
+
* adding an undeclared drawing file still fail.
|
|
21
30
|
*
|
|
22
31
|
* Usage:
|
|
23
32
|
* import { runConventionChecks } from "@multiplatform.one/config/lint";
|
|
@@ -31,9 +40,10 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
|
31
40
|
import { createRequire } from "node:module";
|
|
32
41
|
import { join, relative } from "node:path";
|
|
33
42
|
import { pathToFileURL } from "node:url";
|
|
43
|
+
import { DRAWING_PRAGMA, isDrawingFile, readDrawingRegistry } from "./drawing-registry.mjs";
|
|
34
44
|
|
|
35
45
|
/**
|
|
36
|
-
* @typedef {{ root?: string, apps?: string, features?: string, public?: string }} ConventionRoots
|
|
46
|
+
* @typedef {{ root?: string, apps?: string, features?: string, public?: string, packages?: string }} ConventionRoots
|
|
37
47
|
* @typedef {{ roots?: ConventionRoots }} RunConventionChecksOptions
|
|
38
48
|
*/
|
|
39
49
|
|
|
@@ -45,6 +55,8 @@ let APPS_DIR = join(ROOT, "apps");
|
|
|
45
55
|
let FEATURES_DIR = join(ROOT, "features");
|
|
46
56
|
/** @type {string} */
|
|
47
57
|
let PUBLIC_DIR = join(ROOT, "public");
|
|
58
|
+
/** @type {string} */
|
|
59
|
+
let PACKAGES_DIR = join(ROOT, "packages");
|
|
48
60
|
|
|
49
61
|
/**
|
|
50
62
|
* @param {ConventionRoots} [roots]
|
|
@@ -56,6 +68,7 @@ export function resolveRoots(roots = {}) {
|
|
|
56
68
|
apps: roots.apps ?? join(root, "apps"),
|
|
57
69
|
features: roots.features ?? join(root, "features"),
|
|
58
70
|
public: roots.public ?? join(root, "public"),
|
|
71
|
+
packages: roots.packages ?? join(root, "packages"),
|
|
59
72
|
};
|
|
60
73
|
}
|
|
61
74
|
|
|
@@ -68,6 +81,7 @@ function applyRoots(roots) {
|
|
|
68
81
|
APPS_DIR = resolved.apps;
|
|
69
82
|
FEATURES_DIR = resolved.features;
|
|
70
83
|
PUBLIC_DIR = resolved.public;
|
|
84
|
+
PACKAGES_DIR = resolved.packages;
|
|
71
85
|
sourceFileCache = undefined;
|
|
72
86
|
requireFromRoot = createRequire(join(ROOT, "package.json"));
|
|
73
87
|
}
|
|
@@ -670,6 +684,100 @@ function checkSizeRecipeEscape() {
|
|
|
670
684
|
return violations;
|
|
671
685
|
}
|
|
672
686
|
|
|
687
|
+
// ── Story file shape (MPO-93) ─────────────────────────────────────────────
|
|
688
|
+
|
|
689
|
+
const STORY_FILE_RE = /\.(stories|story)\.(js|jsx|mjs|ts|tsx)$/;
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Names exported through `export { a, b as c }` statements.
|
|
693
|
+
* @param {string} src
|
|
694
|
+
* @returns {Set<string>}
|
|
695
|
+
*/
|
|
696
|
+
function braceExportNames(src) {
|
|
697
|
+
const names = new Set();
|
|
698
|
+
for (const m of src.matchAll(/^export\s*\{([^}]*)\}/gm)) {
|
|
699
|
+
for (const item of m[1].split(",")) {
|
|
700
|
+
const parts = item.trim().split(/\s+as\s+/);
|
|
701
|
+
const name = (parts[1] ?? parts[0] ?? "").trim();
|
|
702
|
+
if (name) names.add(name);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return names;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Storybook's CSF indexer accepts a story file in exactly two shapes: classic
|
|
710
|
+
* CSF (`const meta = {...}; export default meta`) or CSF Factories
|
|
711
|
+
* (`export const meta = preview.meta({...})`, no default export). A file that
|
|
712
|
+
* exports a NAMED `meta` next to `export default` is neither — the indexer
|
|
713
|
+
* takes the named export as the factory shape, finds no factory call, and
|
|
714
|
+
* reports "CSF: missing default export (line 1, col 0)". One such file fails
|
|
715
|
+
* the whole index, the preview build, and `storybook dev` exits non-zero.
|
|
716
|
+
* Nothing else catches it: package builds exclude `.stories.` files, so the
|
|
717
|
+
* pipeline stays green. Three files did exactly this on main (MPO-93).
|
|
718
|
+
*/
|
|
719
|
+
function checkStoryFileShape() {
|
|
720
|
+
/** @type {string[]} */
|
|
721
|
+
const violations = [];
|
|
722
|
+
const scan = (filePath) => {
|
|
723
|
+
if (!STORY_FILE_RE.test(filePath)) return;
|
|
724
|
+
const rel = relative(ROOT, filePath);
|
|
725
|
+
const src = readFileSync(filePath, "utf8");
|
|
726
|
+
const braced = braceExportNames(src);
|
|
727
|
+
const namedMeta =
|
|
728
|
+
src.match(/^export\s+(?:const|let|var|function|class)\s+meta\b/m) ??
|
|
729
|
+
(braced.has("meta") ? src.match(/^export\s*\{[^}]*\bmeta\b[^}]*\}/m) : null);
|
|
730
|
+
const hasDefault = /^export\s+default\b/m.test(src) || braced.has("default");
|
|
731
|
+
const isFactory = /^export\s+(?:const|let|var)\s+meta\b[^=\n]*=\s*[\w$.]+\.meta\s*\(/m.test(
|
|
732
|
+
src,
|
|
733
|
+
);
|
|
734
|
+
if (namedMeta && hasDefault) {
|
|
735
|
+
violations.push(
|
|
736
|
+
`${rel}:${lineAt(src, namedMeta.index ?? 0)} — exports a named \`meta\` beside \`export default\`; drop the \`export\` (\`meta\` is reserved for CSF Factories)`,
|
|
737
|
+
);
|
|
738
|
+
} else if (namedMeta && !isFactory) {
|
|
739
|
+
violations.push(
|
|
740
|
+
`${rel}:${lineAt(src, namedMeta.index ?? 0)} — exports a named \`meta\` that is not a CSF factory call (\`preview.meta(...)\`) and has no default export`,
|
|
741
|
+
);
|
|
742
|
+
} else if (!namedMeta && !hasDefault) {
|
|
743
|
+
violations.push(`${rel} — no default export (classic CSF needs \`export default meta\`)`);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
for (const dir of [PUBLIC_DIR, PACKAGES_DIR, APPS_DIR, FEATURES_DIR]) walkFiles(dir, scan);
|
|
747
|
+
return violations;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* MPO-17: a `// mpo-drawing` pragma is not itself an exemption. The file
|
|
752
|
+
* must also be a DRAWING member in the spec table. Missing spec → skip
|
|
753
|
+
* (shc can run the oxlint rules before it copies the table).
|
|
754
|
+
*/
|
|
755
|
+
function checkUndeclaredDrawings() {
|
|
756
|
+
const specPath = join(ROOT, "docs/theme-propagation-spec.md");
|
|
757
|
+
let registry;
|
|
758
|
+
try {
|
|
759
|
+
registry = readDrawingRegistry(specPath);
|
|
760
|
+
} catch (error) {
|
|
761
|
+
return {
|
|
762
|
+
violations: [`drawing registry: ${error instanceof Error ? error.message : String(error)}`],
|
|
763
|
+
ok: false,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
if (!registry.present) return { violations: [], ok: true };
|
|
767
|
+
/** @type {string[]} */
|
|
768
|
+
const violations = [];
|
|
769
|
+
const scan = (filePath) => {
|
|
770
|
+
if (!/\.(tsx|ts|jsx|js)$/.test(filePath)) return;
|
|
771
|
+
const src = readFileSync(filePath, "utf8");
|
|
772
|
+
if (!DRAWING_PRAGMA.test(src)) return;
|
|
773
|
+
const rel = relative(ROOT, filePath);
|
|
774
|
+
if (isDrawingFile(rel, registry.files) || isDrawingFile(filePath, registry.files)) return;
|
|
775
|
+
violations.push(`${rel} — // mpo-drawing without a DRAWING Members row in ## Drawing files`);
|
|
776
|
+
};
|
|
777
|
+
for (const dir of [PUBLIC_DIR, PACKAGES_DIR, APPS_DIR, FEATURES_DIR]) walkFiles(dir, scan);
|
|
778
|
+
return { violations, ok: true };
|
|
779
|
+
}
|
|
780
|
+
|
|
673
781
|
/**
|
|
674
782
|
* Run structural convention checks over a consumer tree.
|
|
675
783
|
*
|
|
@@ -681,6 +789,8 @@ export function runConventionChecks(options = {}) {
|
|
|
681
789
|
const css = checkCssInApps();
|
|
682
790
|
const twins = checkFeaturesTwins();
|
|
683
791
|
const sizeRecipeEscape = checkSizeRecipeEscape();
|
|
792
|
+
const storyShape = checkStoryFileShape();
|
|
793
|
+
const undeclaredDrawings = checkUndeclaredDrawings();
|
|
684
794
|
const tokens = checkTransitionTokens();
|
|
685
795
|
const styleTokens = checkStyleTokens();
|
|
686
796
|
const themeNames = checkThemeNames();
|
|
@@ -729,6 +839,24 @@ export function runConventionChecks(options = {}) {
|
|
|
729
839
|
console.error("");
|
|
730
840
|
}
|
|
731
841
|
|
|
842
|
+
if (storyShape.length) {
|
|
843
|
+
failed = true;
|
|
844
|
+
console.error(
|
|
845
|
+
'Convention (MPO-93 STORY FILE SHAPE): a story file must be classic CSF (`export default meta`, no named `meta` export) or a CSF factory (`export const meta = preview.meta(...)`). Anything else makes Storybook\'s indexer fail the WHOLE gallery with "CSF: missing default export", and no package build ever sees it.\n',
|
|
846
|
+
);
|
|
847
|
+
for (const msg of storyShape) console.error(` ${msg}`);
|
|
848
|
+
console.error("");
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (undeclaredDrawings.violations.length) {
|
|
852
|
+
failed = true;
|
|
853
|
+
console.error(
|
|
854
|
+
"Convention (MPO-17 DRAWING REGISTRY): `// mpo-drawing` is only legal on a file listed as a DRAWING member in docs/theme-propagation-spec.md. Adding an undeclared drawing file still fails.\n",
|
|
855
|
+
);
|
|
856
|
+
for (const msg of undeclaredDrawings.violations) console.error(` ${msg}`);
|
|
857
|
+
console.error("");
|
|
858
|
+
}
|
|
859
|
+
|
|
732
860
|
if (tokens.violations.length) {
|
|
733
861
|
failed = true;
|
|
734
862
|
console.error(
|
package/oxlint.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export const COLOR_LITERAL: RegExp;
|
|
|
2
2
|
export const PALETTE_FILE: RegExp;
|
|
3
3
|
export const CHROME_PROPS: readonly string[];
|
|
4
4
|
export const LAYOUT_PROPS: readonly string[];
|
|
5
|
+
export const GEOMETRY_PROPS: readonly string[];
|
|
5
6
|
export function isPaletteModule(filename: string | undefined): boolean;
|
|
6
7
|
export function matchColorLiteral(value: unknown): string | undefined;
|
|
7
8
|
export const plugin: {
|