@bleedingdev/modern-js-create 3.2.0-ultramodern.8 → 3.2.0-ultramodern.80

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 (50) hide show
  1. package/README.md +143 -35
  2. package/dist/index.js +4599 -607
  3. package/dist/types/locale/en.d.ts +3 -0
  4. package/dist/types/locale/zh.d.ts +3 -0
  5. package/dist/types/ultramodern-workspace.d.ts +11 -0
  6. package/package.json +6 -6
  7. package/template/.codex/hooks.json +16 -0
  8. package/template/.github/renovate.json +53 -0
  9. package/template/.github/workflows/ultramodern-gates.yml.handlebars +34 -10
  10. package/template/.mise.toml.handlebars +2 -0
  11. package/template/AGENTS.md +9 -6
  12. package/template/README.md +60 -34
  13. package/template/api/effect/index.ts.handlebars +9 -15
  14. package/template/config/public/locales/cs/translation.json +39 -0
  15. package/template/config/public/locales/en/translation.json +39 -0
  16. package/template/lefthook.yml +10 -0
  17. package/template/modern.config.ts.handlebars +41 -21
  18. package/template/oxfmt.config.ts +4 -3
  19. package/template/oxlint.config.ts +4 -4
  20. package/template/package.json.handlebars +43 -34
  21. package/template/pnpm-workspace.yaml +19 -0
  22. package/template/rstest.config.mts +7 -0
  23. package/template/scripts/bootstrap-agent-skills.mjs +63 -31
  24. package/template/scripts/check-i18n-strings.mjs +83 -0
  25. package/template/scripts/validate-ultramodern.mjs.handlebars +373 -35
  26. package/template/shared/effect/api.ts.handlebars +1 -2
  27. package/template/src/modern-app-env.d.ts +2 -0
  28. package/template/src/modern.runtime.ts.handlebars +17 -3
  29. package/template/src/routes/[lang]/page.tsx.handlebars +212 -0
  30. package/template/src/routes/index.css.handlebars +14 -3
  31. package/template/src/routes/layout.tsx.handlebars +2 -1
  32. package/template/tests/tsconfig.json +7 -0
  33. package/template/tests/ultramodern.contract.test.ts.handlebars +78 -0
  34. package/template-workspace/.agents/agent-reference-repos.json +24 -0
  35. package/template-workspace/.agents/skills-lock.json +19 -0
  36. package/template-workspace/.codex/hooks.json +16 -0
  37. package/template-workspace/.github/renovate.json +29 -0
  38. package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +54 -0
  39. package/template-workspace/.gitignore.handlebars +5 -0
  40. package/template-workspace/.mise.toml.handlebars +2 -0
  41. package/template-workspace/AGENTS.md +36 -5
  42. package/template-workspace/README.md.handlebars +33 -10
  43. package/template-workspace/lefthook.yml +10 -0
  44. package/template-workspace/oxfmt.config.ts +13 -3
  45. package/template-workspace/oxlint.config.ts +12 -4
  46. package/template-workspace/pnpm-workspace.yaml +20 -8
  47. package/template-workspace/scripts/bootstrap-agent-skills.mjs +79 -22
  48. package/template-workspace/scripts/setup-agent-reference-repos.mjs +368 -0
  49. package/template/src/routes/page.tsx.handlebars +0 -119
  50. package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -403
@@ -1,57 +1,66 @@
1
1
  {
2
2
  "name": "{{packageName}}",
3
3
  "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "packageManager": "pnpm@{{pnpmVersion}}",
4
7
  "scripts": {
5
8
  "reset": "npx rimraf node_modules ./**/node_modules",
6
9
  "dev": "modern dev",
7
10
  "build": "modern build",
8
11
  "serve": "modern serve",
12
+ "test": "rstest run",
9
13
  "typecheck": "node -e \"const fs = require('node:fs'); const { execFileSync, spawnSync } = require('node:child_process'); const bin = execFileSync('effect-tsgo', ['get-exe-path'], { encoding: 'utf8' }).trim(); if (process.platform !== 'win32') fs.chmodSync(bin, 0o755); const result = spawnSync(bin, ['--noEmit', '-p', 'tsconfig.json'], { stdio: 'inherit' }); process.exit(result.status ?? 1);\"",
14
+ "i18n:check": "node ./scripts/check-i18n-strings.mjs",
15
+ {{#unless isSubproject}}
10
16
  "skills:install": "node ./scripts/bootstrap-agent-skills.mjs",
11
17
  "skills:check": "node ./scripts/bootstrap-agent-skills.mjs --check",
12
- "ultramodern:check": "{{#unless isSubproject}}pnpm format:check && pnpm lint && {{/unless}}pnpm typecheck{{#unless isSubproject}} && pnpm skills:check{{/unless}} && node ./scripts/validate-ultramodern.mjs"{{#unless isSubproject}},
18
+ "postinstall": "node ./scripts/bootstrap-agent-skills.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true)",
19
+ {{/unless}}
20
+ "ultramodern:check": "{{#unless isSubproject}}pnpm format:check && pnpm lint && {{/unless}}pnpm typecheck && pnpm i18n:check && pnpm test{{#unless isSubproject}} && pnpm skills:check{{/unless}} && node ./scripts/validate-ultramodern.mjs"{{#unless isSubproject}},
13
21
  "format": "oxfmt .",
14
22
  "format:check": "oxfmt --check .",
15
23
  "lint": "oxlint .",
16
- "lint:fix": "oxlint . --fix",
17
- "prepare": "simple-git-hooks"{{/unless}}
24
+ "lint:fix": "oxlint . --fix"{{/unless}}
18
25
  },
19
- "engines": {
20
- "node": ">=20"
21
- }{{#unless isSubproject}},
22
- "lint-staged": {
23
- "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
24
- "oxfmt --write",
25
- "oxlint --fix"
26
- ]
27
- },
28
- "simple-git-hooks": {
29
- "pre-commit": "npx lint-staged"
30
- }{{/unless}},
31
26
  "dependencies": {
32
- "@modern-js/runtime": "{{runtimeVersion}}"{{#if isTanstackRouter}},
33
- "@modern-js/plugin-tanstack": "{{pluginTanstackVersion}}",
34
- "@tanstack/react-router": "1.170.1"{{/if}},
35
- "react": "^19.2.3",
36
- "react-dom": "^19.2.0"
27
+ "@modern-js/plugin-i18n": "{{pluginI18nVersion}}",
28
+ {{#if isTanstackRouter}} "@modern-js/plugin-tanstack": "{{pluginTanstackVersion}}",
29
+ {{/if}}
30
+ "@modern-js/runtime": "{{runtimeVersion}}",
31
+ {{#if isTanstackRouter}} "@tanstack/react-router": "{{tanstackRouterVersion}}",
32
+ {{/if}}
33
+ "i18next": "26.2.0",
34
+ "react": "^19.2.6",
35
+ "react-dom": "^19.2.6",
36
+ "react-i18next": "17.0.8"
37
37
  },
38
38
  "devDependencies": {
39
+ "@effect/tsgo": "0.13.0",
40
+ "@modern-js/adapter-rstest": "{{adapterRstestVersion}}",
39
41
  "@modern-js/app-tools": "{{appToolsVersion}}",
40
- "@modern-js/tsconfig": "{{tsconfigVersion}}"{{#if enableBff}},
41
- "@modern-js/plugin-bff": "{{pluginBffVersion}}"{{/if}}{{#if enableTailwind}},
42
- "@tailwindcss/postcss": "^4.1.18",
43
- "postcss": "^8.5.6",
44
- "tailwindcss": "^4.1.18"{{/if}},
45
- "@effect/tsgo": "0.7.3",
46
- "@typescript/native-preview": "7.0.0-dev.20260518.1",
42
+ {{#if enableBff}} "@modern-js/plugin-bff": "{{pluginBffVersion}}",
43
+ {{/if}} "@modern-js/tsconfig": "{{tsconfigVersion}}",
44
+ "@rstest/core": "0.10.2",
45
+ {{#if enableTailwind}}
46
+ "@tailwindcss/postcss": "^{{tailwindPostcssVersion}}",
47
+ {{/if}}
47
48
  "@types/node": "^20",
48
49
  "@types/react": "^19.1.8",
49
- "@types/react-dom": "^19.1.6"{{#unless isSubproject}},
50
- "oxlint": "1.65.0",
51
- "oxfmt": "0.50.0",
52
- "ultracite": "7.7.0",
53
- "lint-staged": "~15.4.0",
54
- "simple-git-hooks": "^2.11.1"{{/unless}},
55
- "rimraf": "^6.0.1"
50
+ "@types/react-dom": "^19.1.6",
51
+ "@typescript/native-preview": "7.0.0-dev.20260527.2",
52
+ "happy-dom": "^20.9.0",
53
+ {{#unless isSubproject}}
54
+ "lefthook": "^2.1.9",
55
+ "oxfmt": "0.51.0",
56
+ "oxlint": "1.66.0",
57
+ {{/unless}}{{#if enableTailwind}} "postcss": "^8.5.6",
58
+ {{/if}} "rimraf": "^6.1.3"{{#if enableTailwind}},
59
+ "tailwindcss": "^{{tailwindVersion}}"{{/if}}{{#unless isSubproject}},
60
+ "ultracite": "7.7.0"{{/unless}}
61
+ },
62
+ "engines": {
63
+ "node": ">=20",
64
+ "pnpm": ">={{pnpmVersion}} <11.6.0"
56
65
  }
57
66
  }
@@ -0,0 +1,19 @@
1
+ minimumReleaseAge: 1440
2
+ minimumReleaseAgeStrict: true
3
+ minimumReleaseAgeIgnoreMissingTime: false
4
+ trustPolicy: no-downgrade
5
+ trustPolicyIgnoreAfter: 1440
6
+ blockExoticSubdeps: true
7
+ engineStrict: true
8
+ pmOnFail: error
9
+ verifyDepsBeforeRun: error
10
+ strictDepBuilds: true
11
+
12
+ allowBuilds:
13
+ '@swc/core': true
14
+ core-js: true
15
+ esbuild: true
16
+ lefthook: true
17
+ msgpackr-extract: true
18
+ sharp: true
19
+ workerd: true
@@ -0,0 +1,7 @@
1
+ import { withModernConfig } from '@modern-js/adapter-rstest';
2
+ import { defineConfig } from '@rstest/core';
3
+
4
+ export default defineConfig({
5
+ extends: withModernConfig(),
6
+ testEnvironment: 'happy-dom',
7
+ });
@@ -8,43 +8,45 @@ const lockPath = path.join(root, '.agents/skills-lock.json');
8
8
  const checkOnly = process.argv.includes('--check');
9
9
  const force = process.argv.includes('--force');
10
10
 
11
- function readJson(filePath) {
12
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
13
- }
11
+ const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf-8'));
14
12
 
15
- function run(command, args, options = {}) {
16
- return execFileSync(command, args, {
13
+ const run = (command, args, options = {}) =>
14
+ execFileSync(command, args, {
17
15
  cwd: options.cwd ?? root,
18
- encoding: 'utf8',
16
+ encoding: 'utf-8',
19
17
  stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
20
18
  });
21
- }
22
19
 
23
- function cloneSource(source, targetDir) {
24
- const repo = source.repository.replace(/^https:\/\/github.com\//, '');
20
+ const removeTree = (dir) =>
21
+ fs.rmSync(dir, {
22
+ force: true,
23
+ maxRetries: 5,
24
+ recursive: true,
25
+ retryDelay: 100,
26
+ });
27
+
28
+ const cloneSource = (source, targetDir) => {
29
+ const repo = source.repository.replace(/^https:\/\/github.com\//u, '');
25
30
  try {
26
31
  run('gh', ['repo', 'clone', repo, targetDir, '--', '--depth', '1'], {
27
32
  stdio: 'inherit',
28
33
  });
29
- return;
30
34
  } catch {
31
35
  run('git', ['clone', '--depth', '1', source.repository, targetDir], {
32
36
  stdio: 'inherit',
33
37
  });
34
38
  }
35
- }
39
+ };
36
40
 
37
- function resolveSkillDir(sourceRoot, skillName) {
41
+ const resolveSkillDir = (sourceRoot, skillName) => {
38
42
  const candidates = [
39
43
  path.join(sourceRoot, skillName),
40
44
  path.join(sourceRoot, 'skills', skillName),
41
45
  path.join(sourceRoot, 'skills', 'engineering', skillName),
42
46
  path.join(sourceRoot, 'skills', 'productivity', skillName),
43
47
  ];
44
- return candidates.find(candidate =>
45
- fs.existsSync(path.join(candidate, 'SKILL.md')),
46
- );
47
- }
48
+ return candidates.find((candidate) => fs.existsSync(path.join(candidate, 'SKILL.md')));
49
+ };
48
50
 
49
51
  if (!fs.existsSync(lockPath)) {
50
52
  console.error('Missing .agents/skills-lock.json');
@@ -53,38 +55,68 @@ if (!fs.existsSync(lockPath)) {
53
55
 
54
56
  const lock = readJson(lockPath);
55
57
  const installDir = path.join(root, lock.installDir ?? '.agents/skills');
56
- const privateSources = (lock.sources ?? []).filter(
57
- source => source.install === 'clone-if-authorized',
58
+ const sources = lock.sources ?? [];
59
+ const requiredCloneSources = sources.filter((source) => source.install === 'clone');
60
+ const optionalCloneSources = sources.filter(
61
+ (source) => source.install === 'clone-if-authorized',
62
+ );
63
+ const requiredSkills = [
64
+ ...(lock.baseline ?? []),
65
+ ...requiredCloneSources.flatMap((source) => source.baseline ?? []),
66
+ ].filter(
67
+ (skill, index, skills) =>
68
+ skills.findIndex((candidate) => candidate.name === skill.name) === index,
58
69
  );
59
70
 
60
71
  if (checkOnly) {
61
- const missing = privateSources.flatMap(source =>
72
+ const missingRequired = requiredSkills
73
+ .map((skill) => skill.name)
74
+ .filter((skillName) => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')));
75
+ const missingOptional = optionalCloneSources.flatMap((source) =>
62
76
  (source.baseline ?? [])
63
- .map(skill => skill.name)
64
- .filter(skillName => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md'))),
77
+ .map((skill) => skill.name)
78
+ .filter((skillName) => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md'))),
65
79
  );
66
- if (missing.length > 0) {
80
+
81
+ if (missingRequired.length > 0) {
82
+ console.error(
83
+ `Required agent skills not installed: ${missingRequired.join(', ')}. Run pnpm skills:install.`,
84
+ );
85
+ process.exit(1);
86
+ }
87
+
88
+ if (missingOptional.length > 0) {
67
89
  console.warn(
68
- `Private skills not installed: ${missing.join(', ')}. Run pnpm skills:install if you have access.`,
90
+ `Private skills not installed: ${missingOptional.join(', ')}. Run pnpm skills:install if you have access.`,
69
91
  );
70
92
  } else {
71
- console.log('Agent skills are installed.');
93
+ console.log('Required and private agent skills are installed.');
94
+ process.exit(0);
72
95
  }
96
+ console.log('Required agent skills are installed.');
73
97
  process.exit(0);
74
98
  }
75
99
 
76
100
  fs.mkdirSync(installDir, { recursive: true });
77
101
 
78
- for (const source of privateSources) {
102
+ for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
79
103
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ultramodern-skills-'));
80
104
  try {
81
- cloneSource(source, tempDir);
105
+ try {
106
+ cloneSource(source, tempDir);
107
+ } catch (error) {
108
+ if (source.install === 'clone-if-authorized') {
109
+ console.warn(
110
+ `Skipping ${source.repository}; current developer may not have access.`,
111
+ );
112
+ continue;
113
+ }
114
+ throw error;
115
+ }
82
116
  for (const skill of source.baseline ?? []) {
83
117
  const sourceSkillDir = resolveSkillDir(tempDir, skill.name);
84
118
  if (!sourceSkillDir) {
85
- throw new Error(
86
- `Skill ${skill.name} not found in ${source.repository}`,
87
- );
119
+ throw new Error(`Skill ${skill.name} not found in ${source.repository}`);
88
120
  }
89
121
  const targetSkillDir = path.join(installDir, skill.name);
90
122
  if (fs.existsSync(targetSkillDir)) {
@@ -92,12 +124,12 @@ for (const source of privateSources) {
92
124
  console.log(`Skipping existing ${skill.name}`);
93
125
  continue;
94
126
  }
95
- fs.rmSync(targetSkillDir, { recursive: true, force: true });
127
+ removeTree(targetSkillDir);
96
128
  }
97
129
  fs.cpSync(sourceSkillDir, targetSkillDir, { recursive: true });
98
130
  console.log(`Installed ${skill.name}`);
99
131
  }
100
132
  } finally {
101
- fs.rmSync(tempDir, { recursive: true, force: true });
133
+ removeTree(tempDir);
102
134
  }
103
135
  }
@@ -0,0 +1,83 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const root = process.cwd();
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
+
11
+ const collectFiles = (directory) => {
12
+ if (!fs.existsSync(directory)) {
13
+ return [];
14
+ }
15
+
16
+ const files = [];
17
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
18
+ if (entry.isDirectory()) {
19
+ if (!ignoredDirectories.has(entry.name)) {
20
+ files.push(...collectFiles(path.join(directory, entry.name)));
21
+ }
22
+ continue;
23
+ }
24
+
25
+ if (entry.isFile() && /\.(jsx|tsx)$/u.test(entry.name) && !entry.name.endsWith('.d.ts')) {
26
+ files.push(path.join(directory, entry.name));
27
+ }
28
+ }
29
+ return files;
30
+ };
31
+
32
+ const lineNumberForIndex = (content, index) => content.slice(0, index).split('\n').length;
33
+ const isIgnoredLine = (content, index) => {
34
+ const lineStart = content.lastIndexOf('\n', index) + 1;
35
+ const lineEnd = content.indexOf('\n', index);
36
+ const currentLineEnd = lineEnd === -1 ? content.length : lineEnd;
37
+ const previousLineStart = content.lastIndexOf('\n', Math.max(0, lineStart - 2)) + 1;
38
+ const nextLineEnd = content.indexOf('\n', currentLineEnd + 1);
39
+ const context = content.slice(
40
+ previousLineStart,
41
+ nextLineEnd === -1 ? content.length : nextLineEnd,
42
+ );
43
+ return /i18n-ignore/u.test(context);
44
+ };
45
+
46
+ const violations = [];
47
+ for (const filePath of scanRoots.flatMap(collectFiles)) {
48
+ const content = fs.readFileSync(filePath, 'utf-8');
49
+ for (const match of content.matchAll(visibleAttributePattern)) {
50
+ if (!isIgnoredLine(content, match.index ?? 0)) {
51
+ violations.push({
52
+ filePath,
53
+ line: lineNumberForIndex(content, match.index ?? 0),
54
+ text: match[1].trim(),
55
+ });
56
+ }
57
+ }
58
+
59
+ for (const match of content.matchAll(jsxTextPattern)) {
60
+ const text = match[1].replaceAll(/\s+/gu, ' ').trim();
61
+ if (text && !isIgnoredLine(content, match.index ?? 0)) {
62
+ violations.push({
63
+ filePath,
64
+ line: lineNumberForIndex(content, match.index ?? 0),
65
+ text,
66
+ });
67
+ }
68
+ }
69
+ }
70
+
71
+ if (violations.length > 0) {
72
+ console.error('Hardcoded user-visible JSX strings found. Move copy to locale JSON files.');
73
+ for (const violation of violations) {
74
+ console.error(
75
+ `${path.relative(root, violation.filePath)}:${violation.line} ${JSON.stringify(
76
+ violation.text,
77
+ )}`,
78
+ );
79
+ }
80
+ process.exit(1);
81
+ }
82
+
83
+ console.log('No hardcoded user-visible JSX strings found.');