@modern-js/create 3.7.0 → 3.8.1

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/index.js CHANGED
@@ -12,7 +12,8 @@ class I18CLILanguageDetector {
12
12
  return LC;
13
13
  }
14
14
  detect() {
15
- const shellLocale = process.env.LC_ALL ?? process.env.LC_MESSAGES ?? process.env.LANG ?? process.env.LANGUAGE ?? Intl.DateTimeFormat().resolvedOptions().locale;
15
+ const env = globalThis.process?.env;
16
+ const shellLocale = env?.LC_ALL ?? env?.LC_MESSAGES ?? env?.LANG ?? env?.LANGUAGE ?? Intl.DateTimeFormat().resolvedOptions().locale;
16
17
  return this.formatShellLocale(shellLocale);
17
18
  }
18
19
  }
@@ -20,6 +21,126 @@ function getLocaleLanguage() {
20
21
  const detector = new I18CLILanguageDetector();
21
22
  return detector.detect();
22
23
  }
24
+ const CLAUDE_IMPORT = '@AGENTS.md';
25
+ const markers = (name)=>({
26
+ begin: `<!-- BEGIN:${name} -->`,
27
+ end: `<!-- END:${name} -->`
28
+ });
29
+ function applyAgentsMd(targetDir, block, markerName) {
30
+ const { begin, end } = markers(markerName);
31
+ const file = node_path.join(targetDir, 'AGENTS.md');
32
+ if (!node_fs.existsSync(file)) {
33
+ node_fs.writeFileSync(file, `${block}\n`, 'utf-8');
34
+ return 'created';
35
+ }
36
+ const content = node_fs.readFileSync(file, 'utf-8');
37
+ const from = content.indexOf(begin);
38
+ const to = content.indexOf(end);
39
+ if (-1 !== from && -1 !== to && to > from) {
40
+ const next = content.slice(0, from) + block + content.slice(to + end.length);
41
+ if (next === content) return 'unchanged';
42
+ node_fs.writeFileSync(file, next, 'utf-8');
43
+ return 'updated';
44
+ }
45
+ const rest = content.replace(/^\s*/, '');
46
+ node_fs.writeFileSync(file, rest ? `${block}\n\n${rest}` : `${block}\n`, 'utf-8');
47
+ return 'added';
48
+ }
49
+ function applyClaudeMd(targetDir) {
50
+ const file = node_path.join(targetDir, 'CLAUDE.md');
51
+ if (!node_fs.existsSync(file)) {
52
+ node_fs.writeFileSync(file, `${CLAUDE_IMPORT}\n`, 'utf-8');
53
+ return 'created';
54
+ }
55
+ const content = node_fs.readFileSync(file, 'utf-8');
56
+ if (content.split('\n').some((line)=>line.trim() === CLAUDE_IMPORT)) return 'unchanged';
57
+ node_fs.writeFileSync(file, `${CLAUDE_IMPORT}\n\n${content.replace(/^\s*/, '')}`, 'utf-8');
58
+ return 'linked';
59
+ }
60
+ function applyAgentFiles(options) {
61
+ const { targetDir, block, markerName } = options;
62
+ if (!node_fs.existsSync(targetDir)) throw new Error(`target directory does not exist: ${targetDir}`);
63
+ return {
64
+ agents: applyAgentsMd(targetDir, block, markerName),
65
+ claude: applyClaudeMd(targetDir)
66
+ };
67
+ }
68
+ const PKG = '@modern-js/app-tools';
69
+ const DOCS_PATH = 'node_modules/@modern-js/app-tools/docs/';
70
+ const BUNDLED_SINCE = '3.8.0';
71
+ function resolveVersion(cwd) {
72
+ const installed = node_path.join(cwd, 'node_modules', PKG, 'package.json');
73
+ if (node_fs.existsSync(installed)) try {
74
+ const { version } = JSON.parse(node_fs.readFileSync(installed, 'utf-8'));
75
+ if ('string' == typeof version) return version;
76
+ } catch {}
77
+ try {
78
+ const pkg = JSON.parse(node_fs.readFileSync(node_path.join(cwd, 'package.json'), 'utf-8'));
79
+ const deps = {
80
+ ...pkg.dependencies,
81
+ ...pkg.devDependencies
82
+ };
83
+ return deps[PKG] ?? null;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+ function parseSemver(version) {
89
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
90
+ return match ? [
91
+ Number(match[1]),
92
+ Number(match[2]),
93
+ Number(match[3])
94
+ ] : null;
95
+ }
96
+ function isAtLeast(version, min) {
97
+ const floor = parseSemver(min);
98
+ if (!floor) return false;
99
+ for(let i = 0; i < 3; i++)if (version[i] !== floor[i]) return version[i] > floor[i];
100
+ return true;
101
+ }
102
+ function supportsBundledDocs(version) {
103
+ if (!version) return false;
104
+ if (/^workspace:|-canary[.-]|-alpha[.-]|-beta[.-]/.test(version)) return true;
105
+ const semver = parseSemver(version);
106
+ return semver ? isAtLeast(semver, BUNDLED_SINCE) : false;
107
+ }
108
+ function isModernProject(cwd) {
109
+ try {
110
+ const pkg = JSON.parse(node_fs.readFileSync(node_path.join(cwd, 'package.json'), 'utf-8'));
111
+ const deps = {
112
+ ...pkg.dependencies,
113
+ ...pkg.devDependencies
114
+ };
115
+ return Object.keys(deps).some((name)=>name.startsWith('@modern-js/'));
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+ function buildBlock(markerName) {
121
+ return [
122
+ `<!-- BEGIN:${markerName} -->`,
123
+ '',
124
+ '# Modern.js: read the docs before you code',
125
+ '',
126
+ `> Documentation: **\`${DOCS_PATH}\`**`,
127
+ `> Index: \`${DOCS_PATH}llms.txt\` — start here when unsure which page to open`,
128
+ '',
129
+ 'These docs ship inside the package, so they match the Modern.js',
130
+ 'version this project installed exactly. Your training data is likely',
131
+ 'outdated — **treat them as the source of truth**, and do not answer',
132
+ 'from memory on Modern.js configuration, APIs or directory conventions.',
133
+ '',
134
+ '**🟢 Read the docs before you touch anything, except for:**',
135
+ '',
136
+ '- Writing ordinary React components (not route components)',
137
+ '- Editing CSS or style files',
138
+ '- Adding utility functions or business logic',
139
+ '- Installing ordinary npm packages (unrelated to Modern.js)',
140
+ '',
141
+ `<!-- END:${markerName} -->`
142
+ ].join('\n');
143
+ }
23
144
  var isArray = Array.isArray;
24
145
  const lodash_es_isArray = isArray;
25
146
  var freeGlobal = 'object' == typeof global && global && global.Object === Object && global;
@@ -442,16 +563,29 @@ const EN_LOCALE = {
442
563
  error: {
443
564
  projectNameEmpty: 'Error: Project name cannot be empty',
444
565
  directoryExists: 'Error: Directory "{projectName}" already exists and is not empty',
445
- createFailed: 'Error creating project:'
566
+ createFailed: 'Error creating project:',
567
+ agentsMdOnlyConflict: 'Error: --agents-md-only only updates the current project and cannot be combined with a project name or --no-agents-md'
446
568
  },
447
569
  message: {
448
570
  welcome: '🚀 Welcome to Modern.js',
449
571
  success: '✨ Created successfully!',
572
+ agentsMd: '✔ AGENTS.md & CLAUDE.md generated — AI coding agents will pick them up automatically. (--no-agents-md to skip)',
450
573
  nextSteps: '📋 Next steps:',
451
574
  step1: 'cd {projectName}',
452
575
  step2: 'pnpm install',
453
576
  step3: 'pnpm dev'
454
577
  },
578
+ agentsCmd: {
579
+ created: '✔ Created {file}',
580
+ updatedBlock: '✔ Updated the modernjs-agent-rules block in {file}',
581
+ addedBlock: '✔ Added the modernjs-agent-rules block to the top of {file}',
582
+ linked: '✔ Added the `@AGENTS.md` import to {file}',
583
+ unchanged: '• {file} is already up to date',
584
+ done: '✨ Done — AI coding agents will read {location}.',
585
+ targetNotFound: 'Error: target directory "{dir}" does not exist',
586
+ notAProject: 'Error: not a Modern.js project — run this in a project root',
587
+ unsupportedVersion: '• @modern-js/app-tools@{version} does not ship bundled docs; nothing was changed. Add https://modernjs.dev/llms.txt to AGENTS.md so AI tools can reach the framework docs, or upgrade to {since} or later and re-run this command'
588
+ },
455
589
  help: {
456
590
  title: '🚀 Modern.js Project Creator',
457
591
  description: 'Create a new Modern.js project with ease',
@@ -462,11 +596,13 @@ const EN_LOCALE = {
462
596
  optionVersion: ' -v, --version Display version information',
463
597
  optionLang: ' -l, --lang Set the language (zh or en)',
464
598
  optionSub: ' -s, --sub Mark as a subproject (package in monorepo)',
599
+ optionNoAgentsMd: ' --no-agents-md Skip generating AGENTS.md / CLAUDE.md for AI coding agents',
600
+ optionAgentsMdOnly: ' --agents-md-only Only add/refresh AGENTS.md / CLAUDE.md in the current project (no scaffolding)',
465
601
  examples: '💡 Examples:',
466
602
  example1: ' create my-app',
467
603
  example2: ' create my-app --lang zh',
468
604
  example3: ' create my-app --sub',
469
- example4: ' create --help',
605
+ example4: ' create --agents-md-only (add/refresh AGENTS.md & CLAUDE.md in an existing project)',
470
606
  moreInfo: '📚 Learn more: https://modernjs.dev'
471
607
  },
472
608
  version: {
@@ -480,16 +616,29 @@ const ZH_LOCALE = {
480
616
  error: {
481
617
  projectNameEmpty: '错误: 项目名称不能为空',
482
618
  directoryExists: '错误: 目录 "{projectName}" 已存在且不为空',
483
- createFailed: '创建项目时出错:'
619
+ createFailed: '创建项目时出错:',
620
+ agentsMdOnlyConflict: '错误: --agents-md-only 只更新当前项目,不能与项目名或 --no-agents-md 同时使用'
484
621
  },
485
622
  message: {
486
623
  welcome: '🚀 欢迎使用 Modern.js',
487
624
  success: '✨ 创建成功!',
625
+ agentsMd: '✔ 已生成 AGENTS.md 和 CLAUDE.md —— AI 编码助手会自动读取。(--no-agents-md 可跳过)',
488
626
  nextSteps: '📋 下一步:',
489
627
  step1: 'cd {projectName}',
490
628
  step2: 'pnpm install',
491
629
  step3: 'pnpm dev'
492
630
  },
631
+ agentsCmd: {
632
+ created: '✔ 已创建 {file}',
633
+ updatedBlock: '✔ 已更新 {file} 中的 modernjs-agent-rules 块',
634
+ addedBlock: '✔ 已在 {file} 顶部添加 modernjs-agent-rules 块',
635
+ linked: '✔ 已向 {file} 添加 `@AGENTS.md` 引用',
636
+ unchanged: '• {file} 已是最新',
637
+ done: '✨ 完成 —— AI 编码助手会读取 {location}。',
638
+ targetNotFound: '错误: 目标目录 "{dir}" 不存在',
639
+ notAProject: '错误: 当前目录不是 Modern.js 项目,请在项目根目录运行',
640
+ unsupportedVersion: '• 当前 @modern-js/app-tools@{version} 不支持随包文档,未修改任何文件。可在 AGENTS.md 中补充 https://modernjs.dev/llms.txt 供 AI 工具获取框架知识,或升级到 {since} 及以上后重新执行本命令'
641
+ },
493
642
  help: {
494
643
  title: '🚀 Modern.js 项目创建工具',
495
644
  description: '快速创建一个新的 Modern.js 项目',
@@ -500,11 +649,13 @@ const ZH_LOCALE = {
500
649
  optionVersion: ' -v, --version 显示版本信息',
501
650
  optionLang: ' -l, --lang 设置语言 (zh 或 en)',
502
651
  optionSub: ' -s, --sub 标记为子项目(monorepo 中的子包)',
652
+ optionNoAgentsMd: ' --no-agents-md 跳过生成 AGENTS.md / CLAUDE.md(AI 编码助手指引文件)',
653
+ optionAgentsMdOnly: ' --agents-md-only 仅为当前项目补齐/更新 AGENTS.md / CLAUDE.md(不创建项目)',
503
654
  examples: '💡 示例:',
504
655
  example1: ' create my-app',
505
656
  example2: ' create my-app --lang zh',
506
657
  example3: ' create my-app --sub',
507
- example4: ' create --help',
658
+ example4: ' create --agents-md-only (为已有项目补齐/更新 AGENTS.md 和 CLAUDE.md)',
508
659
  moreInfo: '📚 更多信息: https://modernjs.dev'
509
660
  },
510
661
  version: {
@@ -516,6 +667,51 @@ const localeKeys = i18n.init('en', {
516
667
  zh: ZH_LOCALE,
517
668
  en: EN_LOCALE
518
669
  });
670
+ const MARKER_NAME = 'modernjs-agent-rules';
671
+ const AGENTS_MESSAGES = {
672
+ created: localeKeys.agentsCmd.created,
673
+ updated: localeKeys.agentsCmd.updatedBlock,
674
+ added: localeKeys.agentsCmd.addedBlock,
675
+ unchanged: localeKeys.agentsCmd.unchanged
676
+ };
677
+ const CLAUDE_MESSAGES = {
678
+ created: localeKeys.agentsCmd.created,
679
+ linked: localeKeys.agentsCmd.linked,
680
+ unchanged: localeKeys.agentsCmd.unchanged
681
+ };
682
+ function runAgentsMd(_templateDir, targetDir) {
683
+ if (!node_fs.existsSync(targetDir)) {
684
+ console.error(i18n.t(localeKeys.agentsCmd.targetNotFound, {
685
+ dir: targetDir
686
+ }));
687
+ process.exit(1);
688
+ }
689
+ if (!isModernProject(targetDir)) {
690
+ console.error(i18n.t(localeKeys.agentsCmd.notAProject));
691
+ process.exit(1);
692
+ }
693
+ const version = resolveVersion(targetDir);
694
+ if (!supportsBundledDocs(version)) return void console.log(i18n.t(localeKeys.agentsCmd.unsupportedVersion, {
695
+ version: version ?? 'unknown',
696
+ since: "3.8.0"
697
+ }));
698
+ const block = buildBlock(MARKER_NAME);
699
+ const result = applyAgentFiles({
700
+ targetDir,
701
+ block,
702
+ markerName: MARKER_NAME
703
+ });
704
+ console.log(i18n.t(AGENTS_MESSAGES[result.agents], {
705
+ file: 'AGENTS.md'
706
+ }));
707
+ console.log(i18n.t(CLAUDE_MESSAGES[result.claude], {
708
+ file: 'CLAUDE.md'
709
+ }));
710
+ console.log('');
711
+ console.log(i18n.t(localeKeys.agentsCmd.done, {
712
+ location: DOCS_PATH
713
+ }));
714
+ }
519
715
  const src_dirname = node_path.dirname(fileURLToPath(import.meta.url));
520
716
  const templateDir = node_path.resolve(src_dirname, '..', 'template');
521
717
  const detectLanguage = ()=>{
@@ -567,6 +763,8 @@ function showHelp() {
567
763
  console.log(i18n.t(localeKeys.help.optionVersion));
568
764
  console.log(i18n.t(localeKeys.help.optionLang));
569
765
  console.log(i18n.t(localeKeys.help.optionSub));
766
+ console.log(i18n.t(localeKeys.help.optionNoAgentsMd));
767
+ console.log(i18n.t(localeKeys.help.optionAgentsMdOnly));
570
768
  console.log('');
571
769
  console.log(i18n.t(localeKeys.help.examples));
572
770
  console.log(i18n.t(localeKeys.help.example1));
@@ -590,12 +788,30 @@ function promptInput(question) {
590
788
  });
591
789
  });
592
790
  }
791
+ const VALUE_FLAGS = [
792
+ '--lang',
793
+ '-l'
794
+ ];
795
+ const BOOLEAN_FLAGS = [
796
+ '--help',
797
+ '-h',
798
+ '--version',
799
+ '-v',
800
+ '--sub',
801
+ '-s',
802
+ '--no-sub',
803
+ '--no-agents-md',
804
+ '--agents-md-only'
805
+ ];
593
806
  function detectSubprojectFlag() {
594
807
  const args = process.argv.slice(2);
595
808
  if (args.includes('--sub') || args.includes('-s')) return true;
596
809
  if (args.includes('--no-sub')) return false;
597
810
  return null;
598
811
  }
812
+ function detectNoAgentsMdFlag() {
813
+ return process.argv.slice(2).includes('--no-agents-md');
814
+ }
599
815
  function isDirectoryEmpty(dirPath) {
600
816
  if (!node_fs.existsSync(dirPath)) return false;
601
817
  try {
@@ -607,7 +823,7 @@ function isDirectoryEmpty(dirPath) {
607
823
  }
608
824
  async function getProjectName() {
609
825
  const args = process.argv.slice(2);
610
- const projectNameArg = args.find((arg, index)=>'--lang' !== arg && '-l' !== arg && '--help' !== arg && '-h' !== arg && '--version' !== arg && '-v' !== arg && '--sub' !== arg && '-s' !== arg && '--no-sub' !== arg && (0 === index || '--lang' !== args[index - 1] && '-l' !== args[index - 1] && '--help' !== args[index - 1] && '-h' !== args[index - 1] && '--version' !== args[index - 1] && '-v' !== args[index - 1] && '--sub' !== args[index - 1] && '-s' !== args[index - 1] && '--no-sub' !== args[index - 1]));
826
+ const projectNameArg = args.find((arg, index)=>!VALUE_FLAGS.includes(arg) && !BOOLEAN_FLAGS.includes(arg) && !(index > 0 && VALUE_FLAGS.includes(args[index - 1])));
611
827
  if (projectNameArg) return {
612
828
  name: projectNameArg,
613
829
  useCurrentDir: false
@@ -631,6 +847,15 @@ async function main() {
631
847
  const args = process.argv.slice(2);
632
848
  if (args.includes('--help') || args.includes('-h')) return void showHelp();
633
849
  if (args.includes('--version') || args.includes('-v')) return void showVersion();
850
+ if (args.includes('--agents-md-only')) {
851
+ const hasProjectName = args.some((arg, index)=>!arg.startsWith('-') && !VALUE_FLAGS.includes(args[index - 1]));
852
+ if (hasProjectName || args.includes('--no-agents-md')) {
853
+ console.error(i18n.t(localeKeys.error.agentsMdOnlyConflict));
854
+ process.exit(1);
855
+ }
856
+ runAgentsMd(templateDir, process.cwd());
857
+ return;
858
+ }
634
859
  console.log(`\n${i18n.t(localeKeys.message.welcome)}\n`);
635
860
  const { name: projectName, useCurrentDir } = await getProjectName();
636
861
  const targetDir = useCurrentDir ? process.cwd() : node_path.isAbsolute(projectName) ? projectName : node_path.resolve(process.cwd(), projectName);
@@ -648,10 +873,12 @@ async function main() {
648
873
  const version = createPackage.version || 'latest';
649
874
  const subprojectFlag = detectSubprojectFlag();
650
875
  const isSubproject = true === subprojectFlag;
876
+ const noAgentsMd = detectNoAgentsMdFlag();
651
877
  copyTemplate(templateDir, targetDir, {
652
878
  packageName: projectName,
653
879
  version,
654
- isSubproject
880
+ isSubproject,
881
+ noAgentsMd
655
882
  });
656
883
  const targetPackageJson = node_path.join(targetDir, 'package.json');
657
884
  const packageJson = JSON.parse(node_fs.readFileSync(targetPackageJson, 'utf-8'));
@@ -673,6 +900,7 @@ async function main() {
673
900
  const dim = '\x1b[2m\x1b[3m';
674
901
  const reset = '\x1b[0m';
675
902
  console.log(`${i18n.t(localeKeys.message.success)}\n`);
903
+ if (!noAgentsMd && !isSubproject) console.log(`${i18n.t(localeKeys.message.agentsMd)}\n`);
676
904
  console.log(i18n.t(localeKeys.message.nextSteps));
677
905
  if (!useCurrentDir) console.log(`${dim} ${i18n.t(localeKeys.message.step1, {
678
906
  projectName
@@ -688,7 +916,13 @@ function copyTemplate(src, dest, options) {
688
916
  '.gitignore.handlebars',
689
917
  'biome.json',
690
918
  '.npmrc',
691
- '.nvmrc'
919
+ '.nvmrc',
920
+ 'AGENTS.md',
921
+ 'CLAUDE.md'
922
+ ];
923
+ const agentFiles = [
924
+ 'AGENTS.md',
925
+ 'CLAUDE.md'
692
926
  ];
693
927
  function copyRecursive(srcDir, destDir) {
694
928
  const entries = node_fs.readdirSync(srcDir, {
@@ -696,6 +930,7 @@ function copyTemplate(src, dest, options) {
696
930
  });
697
931
  for (const entry of entries){
698
932
  if (options.isSubproject && excludeInSubproject.includes(entry.name)) continue;
933
+ if (options.noAgentsMd && agentFiles.includes(entry.name)) continue;
699
934
  const srcPath = node_path.join(srcDir, entry.name);
700
935
  let destPath = node_path.join(destDir, entry.name);
701
936
  if (entry.isDirectory()) {
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "engines": {
20
20
  "node": ">=20"
21
21
  },
22
- "version": "3.7.0",
22
+ "version": "3.8.1",
23
23
  "types": "./dist/types/index.d.ts",
24
24
  "main": "./dist/index.js",
25
25
  "bin": {
@@ -38,7 +38,8 @@
38
38
  "@types/node": "^20",
39
39
  "tsx": "^4.22.4",
40
40
  "typescript": "^5",
41
- "@modern-js/i18n-utils": "3.7.0",
41
+ "@modern-js/i18n-utils": "3.8.1",
42
+ "@scripts/rstest-config": "2.66.0",
42
43
  "@modern-js/rslib": "2.68.10"
43
44
  },
44
45
  "publishConfig": {
@@ -48,6 +49,7 @@
48
49
  "scripts": {
49
50
  "build": "rslib build",
50
51
  "dev": "rslib build -w",
51
- "start": "node ./dist/index.js"
52
+ "start": "node ./dist/index.js",
53
+ "test": "rstest"
52
54
  }
53
55
  }
@@ -0,0 +1,20 @@
1
+ <!-- BEGIN:modernjs-agent-rules -->
2
+
3
+ # Modern.js: read the docs before you code
4
+
5
+ > Documentation: **`node_modules/@modern-js/app-tools/docs/`**
6
+ > Index: `node_modules/@modern-js/app-tools/docs/llms.txt` — start here when unsure which page to open
7
+
8
+ These docs ship inside the package, so they match the Modern.js
9
+ version this project installed exactly. Your training data is likely
10
+ outdated — **treat them as the source of truth**, and do not answer
11
+ from memory on Modern.js configuration, APIs or directory conventions.
12
+
13
+ **🟢 Read the docs before you touch anything, except for:**
14
+
15
+ - Writing ordinary React components (not route components)
16
+ - Editing CSS or style files
17
+ - Adding utility functions or business logic
18
+ - Installing ordinary npm packages (unrelated to Modern.js)
19
+
20
+ <!-- END:modernjs-agent-rules -->
@@ -0,0 +1 @@
1
+ @AGENTS.md