@bleedingdev/modern-js-create 3.2.0-ultramodern.108 → 3.2.0-ultramodern.110
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/dist/cjs/index.cjs +1040 -0
- package/dist/cjs/locale/en.cjs +97 -0
- package/dist/cjs/locale/index.cjs +50 -0
- package/dist/cjs/locale/zh.cjs +97 -0
- package/dist/cjs/ultramodern-checks/cli/i18n-check.cjs +73 -0
- package/dist/cjs/ultramodern-checks/cli/oxlint.cjs +174 -0
- package/dist/cjs/ultramodern-checks/cli/workspace-source-check.cjs +179 -0
- package/dist/cjs/ultramodern-checks/index.cjs +58 -0
- package/dist/cjs/ultramodern-checks/oxlint-plugin.cjs +354 -0
- package/dist/cjs/ultramodern-package-source.cjs +133 -0
- package/dist/cjs/ultramodern-workspace.cjs +5616 -0
- package/dist/esm/index.js +1002 -0
- package/dist/esm/locale/en.js +59 -0
- package/dist/esm/locale/index.js +9 -0
- package/dist/esm/locale/zh.js +59 -0
- package/dist/esm/ultramodern-checks/cli/i18n-check.js +26 -0
- package/dist/esm/ultramodern-checks/cli/oxlint.js +118 -0
- package/dist/esm/ultramodern-checks/cli/workspace-source-check.js +124 -0
- package/dist/esm/ultramodern-checks/index.js +3 -0
- package/dist/esm/ultramodern-checks/oxlint-plugin.js +316 -0
- package/dist/esm/ultramodern-package-source.js +61 -0
- package/dist/esm/ultramodern-workspace.js +5554 -0
- package/dist/esm-node/index.js +1003 -0
- package/dist/esm-node/locale/en.js +60 -0
- package/dist/esm-node/locale/index.js +10 -0
- package/dist/esm-node/locale/zh.js +60 -0
- package/dist/esm-node/ultramodern-checks/cli/i18n-check.js +27 -0
- package/dist/esm-node/ultramodern-checks/cli/oxlint.js +119 -0
- package/dist/esm-node/ultramodern-checks/cli/workspace-source-check.js +125 -0
- package/dist/esm-node/ultramodern-checks/index.js +4 -0
- package/dist/esm-node/ultramodern-checks/oxlint-plugin.js +317 -0
- package/dist/esm-node/ultramodern-package-source.js +62 -0
- package/dist/{index.js → esm-node/ultramodern-workspace.js} +31 -1793
- package/dist/types/ultramodern-checks/cli/i18n-check.d.ts +9 -0
- package/dist/types/ultramodern-checks/cli/oxlint.d.ts +22 -0
- package/dist/types/ultramodern-checks/cli/workspace-source-check.d.ts +8 -0
- package/dist/types/ultramodern-checks/index.d.ts +3 -0
- package/dist/types/ultramodern-checks/oxlint-plugin.d.ts +63 -0
- package/dist/types/ultramodern-package-source.d.ts +2 -2
- package/package.json +49 -8
- package/template/package.json.handlebars +7 -5
- package/template/scripts/check-i18n-strings.mjs +2 -205
- package/template/scripts/validate-ultramodern.mjs.handlebars +27 -9
- package/template/tests/ultramodern.contract.test.ts.handlebars +19 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type SingleAppI18nCheckOptions = {
|
|
2
|
+
readonly cwd?: string;
|
|
3
|
+
readonly targets?: readonly string[];
|
|
4
|
+
};
|
|
5
|
+
export declare const SINGLE_APP_I18N_SUCCESS = "No hardcoded user-visible JSX strings found.";
|
|
6
|
+
export declare const SINGLE_APP_I18N_FAILURE = "Hardcoded user-visible JSX strings found. Move copy to locale JSON files.";
|
|
7
|
+
export declare const runSingleAppI18nCheck: ({ cwd, targets, }?: SingleAppI18nCheckOptions) => number;
|
|
8
|
+
export declare const main: () => void;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type OxlintRuleConfig = string | readonly [
|
|
2
|
+
string,
|
|
3
|
+
{
|
|
4
|
+
readonly [key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
];
|
|
7
|
+
type OxlintRules = {
|
|
8
|
+
readonly [ruleName: string]: OxlintRuleConfig;
|
|
9
|
+
};
|
|
10
|
+
type OxlintRulesOptions = {
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly targets: readonly string[];
|
|
13
|
+
readonly rules: OxlintRules;
|
|
14
|
+
};
|
|
15
|
+
type OxlintRulesResult = {
|
|
16
|
+
readonly exitCode: number;
|
|
17
|
+
readonly stdout: string;
|
|
18
|
+
readonly stderr: string;
|
|
19
|
+
};
|
|
20
|
+
export declare const runOxlintRules: ({ cwd, targets, rules, }: OxlintRulesOptions) => OxlintRulesResult;
|
|
21
|
+
export declare const printOxlintOutput: ({ stdout, stderr, }: OxlintRulesResult) => void;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type WorkspaceSourceCheckOptions = {
|
|
2
|
+
readonly cwd?: string;
|
|
3
|
+
readonly sourceRoots?: readonly string[];
|
|
4
|
+
};
|
|
5
|
+
export declare const WORKSPACE_SOURCE_SUCCESS = "UltraModern i18n and boundary guardrails validated";
|
|
6
|
+
export declare const runWorkspaceSourceCheck: ({ cwd, sourceRoots, }?: WorkspaceSourceCheckOptions) => number;
|
|
7
|
+
export declare const main: () => void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
type AstNode = {
|
|
2
|
+
readonly type?: string;
|
|
3
|
+
readonly parent?: AstNode;
|
|
4
|
+
readonly loc?: {
|
|
5
|
+
readonly start?: {
|
|
6
|
+
readonly line?: number;
|
|
7
|
+
};
|
|
8
|
+
readonly end?: {
|
|
9
|
+
readonly line?: number;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
readonly value?: unknown;
|
|
13
|
+
readonly raw?: unknown;
|
|
14
|
+
readonly name?: unknown;
|
|
15
|
+
readonly openingElement?: AstNode;
|
|
16
|
+
readonly expression?: AstNode;
|
|
17
|
+
readonly expressions?: readonly AstNode[];
|
|
18
|
+
readonly quasis?: readonly AstNode[];
|
|
19
|
+
readonly test?: AstNode;
|
|
20
|
+
readonly consequent?: AstNode;
|
|
21
|
+
readonly alternate?: AstNode;
|
|
22
|
+
readonly arguments?: readonly AstNode[];
|
|
23
|
+
readonly callee?: AstNode;
|
|
24
|
+
readonly object?: AstNode;
|
|
25
|
+
readonly property?: AstNode;
|
|
26
|
+
readonly computed?: boolean;
|
|
27
|
+
};
|
|
28
|
+
type RuleContext = {
|
|
29
|
+
readonly options?: readonly unknown[];
|
|
30
|
+
readonly filename?: string;
|
|
31
|
+
getSourceCode?: () => {
|
|
32
|
+
readonly text?: string;
|
|
33
|
+
getAllComments?: () => readonly AstNode[];
|
|
34
|
+
getText?: (node: AstNode) => string;
|
|
35
|
+
};
|
|
36
|
+
report: (descriptor: {
|
|
37
|
+
readonly node: AstNode;
|
|
38
|
+
readonly message: string;
|
|
39
|
+
}) => void;
|
|
40
|
+
};
|
|
41
|
+
type Rule = {
|
|
42
|
+
readonly meta: {
|
|
43
|
+
readonly type: string;
|
|
44
|
+
readonly docs: {
|
|
45
|
+
readonly description: string;
|
|
46
|
+
};
|
|
47
|
+
readonly schema: unknown[];
|
|
48
|
+
};
|
|
49
|
+
create(context: RuleContext): Record<string, (node: AstNode) => void>;
|
|
50
|
+
};
|
|
51
|
+
declare const plugin: {
|
|
52
|
+
meta: {
|
|
53
|
+
name: string;
|
|
54
|
+
};
|
|
55
|
+
rules: {
|
|
56
|
+
'no-hardcoded-jsx-text': Rule;
|
|
57
|
+
'no-legacy-mf-boundary-attributes': Rule;
|
|
58
|
+
'no-literal-visible-jsx-attributes': Rule;
|
|
59
|
+
'no-manual-locale-copy-branching': Rule;
|
|
60
|
+
'no-split-translation-keys': Rule;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export default plugin;
|
|
@@ -3,8 +3,8 @@ export declare const BLEEDINGDEV_CREATE_PACKAGE = "@bleedingdev/modern-js-create
|
|
|
3
3
|
export declare const BLEEDINGDEV_PACKAGE_SCOPE = "bleedingdev";
|
|
4
4
|
export declare const BLEEDINGDEV_PACKAGE_NAME_PREFIX = "modern-js-";
|
|
5
5
|
export declare const BLEEDINGDEV_FRAMEWORK_VERSION_ENV = "MODERN_CREATE_ULTRAMODERN_FRAMEWORK_VERSION";
|
|
6
|
-
export declare const ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES: readonly ['@modern-js/runtime', '@modern-js/app-tools', '@modern-js/tsconfig', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/plugin-bff', '@modern-js/adapter-rstest'];
|
|
7
|
-
export declare const ULTRAMODERN_WORKSPACE_MODERN_PACKAGES: readonly ['@modern-js/app-tools', '@modern-js/plugin-bff', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/runtime'];
|
|
6
|
+
export declare const ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES: readonly ['@modern-js/create', '@modern-js/runtime', '@modern-js/app-tools', '@modern-js/tsconfig', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/plugin-bff', '@modern-js/adapter-rstest'];
|
|
7
|
+
export declare const ULTRAMODERN_WORKSPACE_MODERN_PACKAGES: readonly ['@modern-js/create', '@modern-js/app-tools', '@modern-js/plugin-bff', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/runtime'];
|
|
8
8
|
export type UltramodernPackageSourceStrategy = 'workspace' | 'install';
|
|
9
9
|
export type ResolvedUltramodernPackageSource = {
|
|
10
10
|
strategy: UltramodernPackageSourceStrategy;
|
package/package.json
CHANGED
|
@@ -21,14 +21,50 @@
|
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
24
|
-
"version": "3.2.0-ultramodern.
|
|
24
|
+
"version": "3.2.0-ultramodern.110",
|
|
25
25
|
"types": "./dist/types/index.d.ts",
|
|
26
|
-
"main": "./dist/index.js",
|
|
26
|
+
"main": "./dist/esm-node/index.js",
|
|
27
27
|
"bin": {
|
|
28
28
|
"create": "bin/run.js"
|
|
29
29
|
},
|
|
30
|
+
"typesVersions": {
|
|
31
|
+
"*": {
|
|
32
|
+
".": [
|
|
33
|
+
"./dist/types/index.d.ts"
|
|
34
|
+
],
|
|
35
|
+
"ultramodern-checks": [
|
|
36
|
+
"./dist/types/ultramodern-checks/index.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"ultramodern-checks/oxlint-plugin": [
|
|
39
|
+
"./dist/types/ultramodern-checks/oxlint-plugin.d.ts"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
30
43
|
"exports": {
|
|
31
|
-
".":
|
|
44
|
+
".": {
|
|
45
|
+
"types": "./dist/types/index.d.ts",
|
|
46
|
+
"node": {
|
|
47
|
+
"import": "./dist/esm-node/index.js",
|
|
48
|
+
"require": "./dist/cjs/index.cjs"
|
|
49
|
+
},
|
|
50
|
+
"default": "./dist/esm-node/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./ultramodern-checks": {
|
|
53
|
+
"types": "./dist/types/ultramodern-checks/index.d.ts",
|
|
54
|
+
"node": {
|
|
55
|
+
"import": "./dist/esm-node/ultramodern-checks/index.js",
|
|
56
|
+
"require": "./dist/cjs/ultramodern-checks/index.cjs"
|
|
57
|
+
},
|
|
58
|
+
"default": "./dist/esm-node/ultramodern-checks/index.js"
|
|
59
|
+
},
|
|
60
|
+
"./ultramodern-checks/oxlint-plugin": {
|
|
61
|
+
"types": "./dist/types/ultramodern-checks/oxlint-plugin.d.ts",
|
|
62
|
+
"node": {
|
|
63
|
+
"import": "./dist/esm-node/ultramodern-checks/oxlint-plugin.js",
|
|
64
|
+
"require": "./dist/cjs/ultramodern-checks/oxlint-plugin.cjs"
|
|
65
|
+
},
|
|
66
|
+
"default": "./dist/esm-node/ultramodern-checks/oxlint-plugin.js"
|
|
67
|
+
}
|
|
32
68
|
},
|
|
33
69
|
"files": [
|
|
34
70
|
"template",
|
|
@@ -36,12 +72,16 @@
|
|
|
36
72
|
"dist",
|
|
37
73
|
"bin.js"
|
|
38
74
|
],
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"oxlint": "1.68.0"
|
|
77
|
+
},
|
|
39
78
|
"devDependencies": {
|
|
40
79
|
"@rslib/core": "0.21.5",
|
|
41
80
|
"@types/node": "^25.9.1",
|
|
42
81
|
"@typescript/native-preview": "7.0.0-dev.20260606.1",
|
|
43
82
|
"tsx": "^4.22.3",
|
|
44
|
-
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.
|
|
83
|
+
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.110",
|
|
84
|
+
"@scripts/rstest-config": "2.66.0"
|
|
45
85
|
},
|
|
46
86
|
"publishConfig": {
|
|
47
87
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -49,11 +89,12 @@
|
|
|
49
89
|
},
|
|
50
90
|
"modern:source": "./src/index.ts",
|
|
51
91
|
"scripts": {
|
|
52
|
-
"build": "rslib build && pnpm -w tsgo:dts \"$PWD\"",
|
|
53
|
-
"dev": "rslib build -w",
|
|
54
|
-
"start": "node ./dist/index.js"
|
|
92
|
+
"build": "rm -rf dist && rslib build -c rslibconfig.mts && pnpm -w tsgo:dts \"$PWD\"",
|
|
93
|
+
"dev": "rslib build -c rslibconfig.mts -w",
|
|
94
|
+
"start": "node ./dist/esm-node/index.js",
|
|
95
|
+
"test": "rm -rf dist && rslib build -c rslibconfig.mts && rstest --passWithNoTests"
|
|
55
96
|
},
|
|
56
97
|
"ultramodern": {
|
|
57
|
-
"frameworkVersion": "3.2.0-ultramodern.
|
|
98
|
+
"frameworkVersion": "3.2.0-ultramodern.110"
|
|
58
99
|
}
|
|
59
100
|
}
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"skills:check": "node ./scripts/bootstrap-agent-skills.mjs --check",
|
|
18
18
|
"postinstall": "oxfmt . && node ./scripts/bootstrap-agent-skills.mjs",
|
|
19
19
|
{{/unless}}
|
|
20
|
-
"ultramodern:check": "
|
|
20
|
+
"ultramodern:check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm i18n:check && pnpm test{{#unless isSubproject}} && pnpm skills:check{{/unless}} && node ./scripts/validate-ultramodern.mjs",
|
|
21
21
|
"format": "oxfmt .",
|
|
22
22
|
"format:check": "oxfmt --check .",
|
|
23
23
|
"lint": "oxlint .",
|
|
24
|
-
"lint:fix": "oxlint . --fix"
|
|
24
|
+
"lint:fix": "oxlint . --fix"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@modern-js/plugin-i18n": "{{pluginI18nVersion}}",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@effect/tsgo": "{{effectTsgoVersion}}",
|
|
40
40
|
"@modern-js/adapter-rstest": "{{adapterRstestVersion}}",
|
|
41
41
|
"@modern-js/app-tools": "{{appToolsVersion}}",
|
|
42
|
+
"@modern-js/create": "{{createVersion}}",
|
|
42
43
|
{{#if enableBff}} "@modern-js/plugin-bff": "{{pluginBffVersion}}",
|
|
43
44
|
{{/if}} "@modern-js/tsconfig": "{{tsconfigVersion}}",
|
|
44
45
|
"@rstest/core": "{{rstestCoreVersion}}",
|
|
@@ -52,12 +53,13 @@
|
|
|
52
53
|
"happy-dom": "{{happyDomVersion}}",
|
|
53
54
|
{{#unless isSubproject}}
|
|
54
55
|
"lefthook": "^2.1.9",
|
|
56
|
+
{{/unless}}
|
|
55
57
|
"oxfmt": "{{oxfmtVersion}}",
|
|
56
58
|
"oxlint": "{{oxlintVersion}}",
|
|
57
|
-
{{
|
|
59
|
+
{{#if enableTailwind}} "postcss": "{{postcssVersion}}",
|
|
58
60
|
{{/if}} "rimraf": "^6.1.3"{{#if enableTailwind}},
|
|
59
|
-
"tailwindcss": "^{{tailwindVersion}}"{{/if}}
|
|
60
|
-
"ultracite": "{{ultraciteVersion}}"
|
|
61
|
+
"tailwindcss": "^{{tailwindVersion}}"{{/if}},
|
|
62
|
+
"ultracite": "{{ultraciteVersion}}"
|
|
61
63
|
},
|
|
62
64
|
"engines": {
|
|
63
65
|
"node": ">=20",
|
|
@@ -1,206 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from 'node:path';
|
|
1
|
+
import { runSingleAppI18nCheck } from '@modern-js/create/ultramodern-checks';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
const scanRoots = ['src'].map((scanRoot) => path.join(root, scanRoot));
|
|
6
|
-
const ignoredDirectories = new Set(['.modern', '.modernjs', 'dist', 'node_modules']);
|
|
7
|
-
const visibleAttributePattern =
|
|
8
|
-
/\s(?:aria-label|alt|placeholder|title)=["']([^"']*[A-Za-z][^"']*)["']/gu;
|
|
9
|
-
const jsxTextPattern = />([^<>{}]*[A-Za-z][^<>{}]*)</gu;
|
|
10
|
-
const jsxIntrinsicTags = new Set([
|
|
11
|
-
'a',
|
|
12
|
-
'abbr',
|
|
13
|
-
'address',
|
|
14
|
-
'area',
|
|
15
|
-
'article',
|
|
16
|
-
'aside',
|
|
17
|
-
'audio',
|
|
18
|
-
'b',
|
|
19
|
-
'blockquote',
|
|
20
|
-
'body',
|
|
21
|
-
'br',
|
|
22
|
-
'button',
|
|
23
|
-
'canvas',
|
|
24
|
-
'caption',
|
|
25
|
-
'cite',
|
|
26
|
-
'code',
|
|
27
|
-
'col',
|
|
28
|
-
'colgroup',
|
|
29
|
-
'data',
|
|
30
|
-
'datalist',
|
|
31
|
-
'dd',
|
|
32
|
-
'del',
|
|
33
|
-
'details',
|
|
34
|
-
'dfn',
|
|
35
|
-
'dialog',
|
|
36
|
-
'div',
|
|
37
|
-
'dl',
|
|
38
|
-
'dt',
|
|
39
|
-
'em',
|
|
40
|
-
'fieldset',
|
|
41
|
-
'figcaption',
|
|
42
|
-
'figure',
|
|
43
|
-
'footer',
|
|
44
|
-
'form',
|
|
45
|
-
'h1',
|
|
46
|
-
'h2',
|
|
47
|
-
'h3',
|
|
48
|
-
'h4',
|
|
49
|
-
'h5',
|
|
50
|
-
'h6',
|
|
51
|
-
'head',
|
|
52
|
-
'header',
|
|
53
|
-
'hr',
|
|
54
|
-
'html',
|
|
55
|
-
'i',
|
|
56
|
-
'iframe',
|
|
57
|
-
'img',
|
|
58
|
-
'input',
|
|
59
|
-
'label',
|
|
60
|
-
'legend',
|
|
61
|
-
'li',
|
|
62
|
-
'link',
|
|
63
|
-
'main',
|
|
64
|
-
'mark',
|
|
65
|
-
'menu',
|
|
66
|
-
'meta',
|
|
67
|
-
'meter',
|
|
68
|
-
'nav',
|
|
69
|
-
'ol',
|
|
70
|
-
'option',
|
|
71
|
-
'p',
|
|
72
|
-
'picture',
|
|
73
|
-
'pre',
|
|
74
|
-
'progress',
|
|
75
|
-
'q',
|
|
76
|
-
'script',
|
|
77
|
-
'section',
|
|
78
|
-
'select',
|
|
79
|
-
'small',
|
|
80
|
-
'source',
|
|
81
|
-
'span',
|
|
82
|
-
'strong',
|
|
83
|
-
'style',
|
|
84
|
-
'summary',
|
|
85
|
-
'svg',
|
|
86
|
-
'table',
|
|
87
|
-
'tbody',
|
|
88
|
-
'td',
|
|
89
|
-
'template',
|
|
90
|
-
'textarea',
|
|
91
|
-
'tfoot',
|
|
92
|
-
'th',
|
|
93
|
-
'thead',
|
|
94
|
-
'time',
|
|
95
|
-
'title',
|
|
96
|
-
'tr',
|
|
97
|
-
'u',
|
|
98
|
-
'ul',
|
|
99
|
-
'video',
|
|
100
|
-
]);
|
|
101
|
-
|
|
102
|
-
const collectFiles = (directory) => {
|
|
103
|
-
if (!fs.existsSync(directory)) {
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const files = [];
|
|
108
|
-
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
109
|
-
if (entry.isDirectory()) {
|
|
110
|
-
if (!ignoredDirectories.has(entry.name)) {
|
|
111
|
-
files.push(...collectFiles(path.join(directory, entry.name)));
|
|
112
|
-
}
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (entry.isFile() && /\.(jsx|tsx)$/u.test(entry.name) && !entry.name.endsWith('.d.ts')) {
|
|
117
|
-
files.push(path.join(directory, entry.name));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return files;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const lineNumberForIndex = (content, index) => content.slice(0, index).split('\n').length;
|
|
124
|
-
const isCodeElementText = (content, index) => {
|
|
125
|
-
const tagStart = content.lastIndexOf('<', index);
|
|
126
|
-
if (tagStart === -1) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
return /^<code(?:\s|>)/u.test(content.slice(tagStart, index));
|
|
130
|
-
};
|
|
131
|
-
const isJsxTagEnd = (content, index) => {
|
|
132
|
-
const tagStart = content.lastIndexOf('<', index);
|
|
133
|
-
if (tagStart === -1 || content.slice(tagStart + 1, index).includes('<')) {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
const match = content
|
|
137
|
-
.slice(tagStart, index + 1)
|
|
138
|
-
.match(/^<\/?\s*([A-Za-z][\w:.-]*)\b[^<>]*>$/u);
|
|
139
|
-
if (!match) {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
const [, tagName] = match;
|
|
143
|
-
return (
|
|
144
|
-
/^[A-Z]/u.test(tagName) ||
|
|
145
|
-
tagName.includes('-') ||
|
|
146
|
-
jsxIntrinsicTags.has(tagName)
|
|
147
|
-
);
|
|
148
|
-
};
|
|
149
|
-
const isIgnoredLine = (content, index) => {
|
|
150
|
-
const lineStart = content.lastIndexOf('\n', index) + 1;
|
|
151
|
-
const lineEnd = content.indexOf('\n', index);
|
|
152
|
-
const currentLineEnd = lineEnd === -1 ? content.length : lineEnd;
|
|
153
|
-
const previousLineStart = content.lastIndexOf('\n', Math.max(0, lineStart - 2)) + 1;
|
|
154
|
-
const nextLineEnd = content.indexOf('\n', currentLineEnd + 1);
|
|
155
|
-
const context = content.slice(
|
|
156
|
-
previousLineStart,
|
|
157
|
-
nextLineEnd === -1 ? content.length : nextLineEnd,
|
|
158
|
-
);
|
|
159
|
-
return /i18n-ignore/u.test(context);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const violations = [];
|
|
163
|
-
for (const filePath of scanRoots.flatMap(collectFiles)) {
|
|
164
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
165
|
-
for (const match of content.matchAll(visibleAttributePattern)) {
|
|
166
|
-
const [, visibleText] = match;
|
|
167
|
-
if (!isIgnoredLine(content, match.index ?? 0)) {
|
|
168
|
-
violations.push({
|
|
169
|
-
filePath,
|
|
170
|
-
line: lineNumberForIndex(content, match.index ?? 0),
|
|
171
|
-
text: visibleText.trim(),
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
for (const match of content.matchAll(jsxTextPattern)) {
|
|
177
|
-
const [, jsxText] = match;
|
|
178
|
-
const text = jsxText.replaceAll(/\s+/gu, ' ').trim();
|
|
179
|
-
if (
|
|
180
|
-
text &&
|
|
181
|
-
!isIgnoredLine(content, match.index ?? 0) &&
|
|
182
|
-
isJsxTagEnd(content, match.index ?? 0) &&
|
|
183
|
-
!isCodeElementText(content, match.index ?? 0)
|
|
184
|
-
) {
|
|
185
|
-
violations.push({
|
|
186
|
-
filePath,
|
|
187
|
-
line: lineNumberForIndex(content, match.index ?? 0),
|
|
188
|
-
text,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (violations.length > 0) {
|
|
195
|
-
console.error('Hardcoded user-visible JSX strings found. Move copy to locale JSON files.');
|
|
196
|
-
for (const violation of violations) {
|
|
197
|
-
console.error(
|
|
198
|
-
`${path.relative(root, violation.filePath)}:${violation.line} ${JSON.stringify(
|
|
199
|
-
violation.text,
|
|
200
|
-
)}`,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
console.log('No hardcoded user-visible JSX strings found.');
|
|
3
|
+
process.exitCode = runSingleAppI18nCheck();
|
|
@@ -97,12 +97,12 @@ const requiredPaths = [
|
|
|
97
97
|
'.github/renovate.json',
|
|
98
98
|
'.github/workflows/ultramodern-gates.yml',
|
|
99
99
|
'lefthook.yml',
|
|
100
|
-
'oxlint.config.ts',
|
|
101
|
-
'oxfmt.config.ts',
|
|
102
100
|
'scripts/bootstrap-agent-skills.mjs',
|
|
103
101
|
{{/unless}}
|
|
104
102
|
'.mise.toml',
|
|
105
103
|
'.modernjs/ultramodern-package-source.json',
|
|
104
|
+
'oxlint.config.ts',
|
|
105
|
+
'oxfmt.config.ts',
|
|
106
106
|
'pnpm-workspace.yaml',
|
|
107
107
|
'rstest.config.mts',
|
|
108
108
|
'scripts/check-i18n-strings.mjs',
|
|
@@ -349,14 +349,12 @@ const skillsLock = JSON.parse(
|
|
|
349
349
|
);
|
|
350
350
|
{{/unless}}
|
|
351
351
|
const requiredScripts = {
|
|
352
|
-
{{#unless isSubproject}}
|
|
353
352
|
format: 'oxfmt .',
|
|
354
353
|
'format:check': 'oxfmt --check .',
|
|
355
|
-
{{/unless}}
|
|
356
354
|
'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
|
|
357
|
-
{{#unless isSubproject}}
|
|
358
355
|
lint: 'oxlint .',
|
|
359
356
|
'lint:fix': 'oxlint . --fix',
|
|
357
|
+
{{#unless isSubproject}}
|
|
360
358
|
postinstall: 'oxfmt . && node ./scripts/bootstrap-agent-skills.mjs',
|
|
361
359
|
'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
|
|
362
360
|
'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
|
|
@@ -371,6 +369,15 @@ for (const [scriptName, scriptCommand] of Object.entries(requiredScripts)) {
|
|
|
371
369
|
}
|
|
372
370
|
}
|
|
373
371
|
|
|
372
|
+
const i18nCheckScript = readText('scripts/check-i18n-strings.mjs');
|
|
373
|
+
if (
|
|
374
|
+
!i18nCheckScript.includes("from '@modern-js/create/ultramodern-checks'") ||
|
|
375
|
+
!i18nCheckScript.includes('runSingleAppI18nCheck')
|
|
376
|
+
) {
|
|
377
|
+
console.error('i18n:check must call @modern-js/create/ultramodern-checks');
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
|
|
374
381
|
if (
|
|
375
382
|
!packageJson.scripts?.typecheck?.includes('effect-tsgo') ||
|
|
376
383
|
!packageJson.scripts.typecheck.includes('get-exe-path')
|
|
@@ -379,8 +386,10 @@ if (
|
|
|
379
386
|
process.exit(1);
|
|
380
387
|
}
|
|
381
388
|
|
|
382
|
-
|
|
383
|
-
|
|
389
|
+
const expectedUltramodernCheck =
|
|
390
|
+
'pnpm format:check && pnpm lint && pnpm typecheck && pnpm i18n:check && pnpm test{{#unless isSubproject}} && pnpm skills:check{{/unless}} && node ./scripts/validate-ultramodern.mjs';
|
|
391
|
+
if (packageJson.scripts?.['ultramodern:check'] !== expectedUltramodernCheck) {
|
|
392
|
+
console.error('ultramodern:check must run format, lint, typecheck, i18n, tests, and contract validation');
|
|
384
393
|
process.exit(1);
|
|
385
394
|
}
|
|
386
395
|
|
|
@@ -572,6 +581,14 @@ for (const section of ['dependencies', 'devDependencies']) {
|
|
|
572
581
|
}
|
|
573
582
|
}
|
|
574
583
|
|
|
584
|
+
if (
|
|
585
|
+
packageJson.devDependencies?.['@modern-js/create'] !==
|
|
586
|
+
expectedModernDependency('@modern-js/create')
|
|
587
|
+
) {
|
|
588
|
+
console.error('devDependencies.@modern-js/create must match package source metadata');
|
|
589
|
+
process.exit(1);
|
|
590
|
+
}
|
|
591
|
+
|
|
575
592
|
for (const dependency of ['@modern-js/plugin-i18n', 'i18next', 'react-i18next']) {
|
|
576
593
|
if (!packageJson.dependencies?.[dependency]) {
|
|
577
594
|
console.error(`Missing dependency: ${dependency}`);
|
|
@@ -582,6 +599,7 @@ for (const dependency of ['@modern-js/plugin-i18n', 'i18next', 'react-i18next'])
|
|
|
582
599
|
for (const dependency of [
|
|
583
600
|
'@effect/tsgo',
|
|
584
601
|
'@modern-js/adapter-rstest',
|
|
602
|
+
'@modern-js/create',
|
|
585
603
|
'@rstest/core',
|
|
586
604
|
'@typescript/native-preview',
|
|
587
605
|
'happy-dom',
|
|
@@ -590,11 +608,11 @@ for (const dependency of [
|
|
|
590
608
|
'postcss',
|
|
591
609
|
'tailwindcss',
|
|
592
610
|
{{/if}}
|
|
593
|
-
{{#unless isSubproject}}
|
|
594
|
-
'lefthook',
|
|
595
611
|
'oxlint',
|
|
596
612
|
'oxfmt',
|
|
597
613
|
'ultracite',
|
|
614
|
+
{{#unless isSubproject}}
|
|
615
|
+
'lefthook',
|
|
598
616
|
{{/unless}}
|
|
599
617
|
]) {
|
|
600
618
|
if (!packageJson.devDependencies?.[dependency]) {
|
|
@@ -15,6 +15,8 @@ describe('generated UltraModern contract', () => {
|
|
|
15
15
|
);
|
|
16
16
|
expect(fs.existsSync(path.join(root, 'src/routes/page.tsx'))).toBe(false);
|
|
17
17
|
expect(fs.existsSync(path.join(root, 'src/routes/layout.tsx'))).toBe(true);
|
|
18
|
+
expect(fs.existsSync(path.join(root, 'oxlint.config.ts'))).toBe(true);
|
|
19
|
+
expect(fs.existsSync(path.join(root, 'oxfmt.config.ts'))).toBe(true);
|
|
18
20
|
{{#if enableTailwind}}
|
|
19
21
|
expect(fs.existsSync(path.join(root, 'postcss.config.mjs'))).toBe(true);
|
|
20
22
|
expect(fs.existsSync(path.join(root, 'tailwind.config.ts'))).toBe(true);
|
|
@@ -86,6 +88,7 @@ describe('generated UltraModern contract', () => {
|
|
|
86
88
|
const packageJson = readJson<{
|
|
87
89
|
dependencies?: Record<string, string>;
|
|
88
90
|
devDependencies?: Record<string, string>;
|
|
91
|
+
scripts?: Record<string, string>;
|
|
89
92
|
modernjs?: {
|
|
90
93
|
packageSource?: {
|
|
91
94
|
config?: string;
|
|
@@ -125,6 +128,22 @@ describe('generated UltraModern contract', () => {
|
|
|
125
128
|
expect(
|
|
126
129
|
packageJson.devDependencies?.['@modern-js/adapter-rstest'],
|
|
127
130
|
).toBeTruthy();
|
|
131
|
+
expect(
|
|
132
|
+
packageJson.devDependencies?.['@modern-js/create'],
|
|
133
|
+
).toBeTruthy();
|
|
134
|
+
expect(readText('scripts/check-i18n-strings.mjs')).toContain(
|
|
135
|
+
"from '@modern-js/create/ultramodern-checks'",
|
|
136
|
+
);
|
|
137
|
+
expect(packageJson.scripts?.format).toBe('oxfmt .');
|
|
138
|
+
expect(packageJson.scripts?.['format:check']).toBe('oxfmt --check .');
|
|
139
|
+
expect(packageJson.scripts?.lint).toBe('oxlint .');
|
|
140
|
+
expect(packageJson.scripts?.['lint:fix']).toBe('oxlint . --fix');
|
|
141
|
+
expect(packageJson.scripts?.['ultramodern:check']).toContain(
|
|
142
|
+
'pnpm format:check && pnpm lint',
|
|
143
|
+
);
|
|
144
|
+
expect(packageJson.devDependencies?.oxfmt).toBe('0.53.0');
|
|
145
|
+
expect(packageJson.devDependencies?.oxlint).toBe('1.68.0');
|
|
146
|
+
expect(packageJson.devDependencies?.ultracite).toBe('7.8.1');
|
|
128
147
|
{{#if enableTailwind}}
|
|
129
148
|
expect(packageJson.devDependencies?.tailwindcss).toBe('^4.3.0');
|
|
130
149
|
expect(packageJson.devDependencies?.['@tailwindcss/postcss']).toBe(
|