@bleedingdev/modern-js-create 3.2.0-ultramodern.11 → 3.2.0-ultramodern.111

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 (75) hide show
  1. package/README.md +158 -35
  2. package/bin/run.js +0 -0
  3. package/dist/cjs/index.cjs +1042 -0
  4. package/dist/cjs/locale/en.cjs +97 -0
  5. package/dist/cjs/locale/index.cjs +50 -0
  6. package/dist/cjs/locale/zh.cjs +97 -0
  7. package/dist/cjs/ultramodern-package-source.cjs +135 -0
  8. package/dist/cjs/ultramodern-workspace.cjs +5623 -0
  9. package/dist/esm/index.js +1004 -0
  10. package/dist/esm/locale/en.js +59 -0
  11. package/dist/esm/locale/index.js +9 -0
  12. package/dist/esm/locale/zh.js +59 -0
  13. package/dist/esm/ultramodern-package-source.js +63 -0
  14. package/dist/esm/ultramodern-workspace.js +5561 -0
  15. package/dist/esm-node/index.js +1005 -0
  16. package/dist/esm-node/locale/en.js +60 -0
  17. package/dist/esm-node/locale/index.js +10 -0
  18. package/dist/esm-node/locale/zh.js +60 -0
  19. package/dist/esm-node/ultramodern-package-source.js +64 -0
  20. package/dist/esm-node/ultramodern-workspace.js +5562 -0
  21. package/dist/types/locale/en.d.ts +3 -0
  22. package/dist/types/locale/index.d.ts +117 -2
  23. package/dist/types/locale/zh.d.ts +3 -0
  24. package/dist/types/ultramodern-package-source.d.ts +28 -0
  25. package/dist/types/ultramodern-workspace.d.ts +12 -2
  26. package/package.json +27 -11
  27. package/template/.codex/hooks.json +16 -0
  28. package/template/.github/renovate.json +53 -0
  29. package/template/.github/workflows/ultramodern-gates.yml.handlebars +34 -10
  30. package/template/.mise.toml.handlebars +2 -0
  31. package/template/AGENTS.md +9 -6
  32. package/template/README.md +66 -34
  33. package/template/api/effect/index.ts.handlebars +20 -9
  34. package/template/api/lambda/hello.ts.handlebars +5 -5
  35. package/template/config/favicon.svg +5 -0
  36. package/template/config/public/assets/ultramodern-logo.svg +6 -0
  37. package/template/config/public/locales/cs/translation.json +44 -0
  38. package/template/config/public/locales/en/translation.json +44 -0
  39. package/template/lefthook.yml +10 -0
  40. package/template/modern.config.ts.handlebars +35 -3
  41. package/template/oxfmt.config.ts +8 -1
  42. package/template/oxlint.config.ts +8 -1
  43. package/template/package.json.handlebars +37 -30
  44. package/template/pnpm-workspace.yaml +34 -0
  45. package/template/rstest.config.mts +5 -0
  46. package/template/scripts/bootstrap-agent-skills.mjs +148 -15
  47. package/template/scripts/check-i18n-strings.mjs +3 -0
  48. package/template/scripts/validate-ultramodern.mjs.handlebars +495 -3
  49. package/template/src/modern-app-env.d.ts +2 -0
  50. package/template/src/modern.runtime.ts.handlebars +17 -1
  51. package/template/src/routes/[lang]/page.tsx.handlebars +209 -0
  52. package/template/src/routes/index.css.handlebars +192 -55
  53. package/template/src/routes/layout.tsx.handlebars +2 -1
  54. package/template/tailwind.config.ts.handlebars +1 -1
  55. package/template/tests/tsconfig.json +7 -0
  56. package/template/tests/ultramodern.contract.test.ts.handlebars +163 -0
  57. package/template/tsconfig.json +2 -1
  58. package/template-workspace/.agents/agent-reference-repos.json +24 -0
  59. package/template-workspace/.agents/skills-lock.json +19 -0
  60. package/template-workspace/.codex/hooks.json +16 -0
  61. package/template-workspace/.github/renovate.json +29 -0
  62. package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +54 -0
  63. package/template-workspace/.gitignore.handlebars +5 -0
  64. package/template-workspace/.mise.toml.handlebars +2 -0
  65. package/template-workspace/AGENTS.md +36 -5
  66. package/template-workspace/README.md.handlebars +70 -11
  67. package/template-workspace/lefthook.yml +10 -0
  68. package/template-workspace/oxfmt.config.ts +1 -0
  69. package/template-workspace/oxlint.config.ts +1 -0
  70. package/template-workspace/pnpm-workspace.yaml +31 -8
  71. package/template-workspace/scripts/bootstrap-agent-skills.mjs +190 -21
  72. package/template-workspace/scripts/setup-agent-reference-repos.mjs +370 -0
  73. package/dist/index.js +0 -2474
  74. package/template/src/routes/page.tsx.handlebars +0 -136
  75. package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -405
@@ -0,0 +1,44 @@
1
+ {
2
+ "home": {
3
+ "bff": {
4
+ "response": "Odpoved Effect HttpApi:"
5
+ },
6
+ "cards": {
7
+ "bff": {
8
+ "body": "Pridej serverovou logiku, kdyz route potrebuje typovana data nebo mutace.",
9
+ "title": "BFF + Effect"
10
+ },
11
+ "config": {
12
+ "body": "Upravuj vychozi hodnoty v modern.config.ts podle rustu aplikace.",
13
+ "title": "Konfigurace presetUltramodern"
14
+ },
15
+ "gates": {
16
+ "body": "Pred releasem spust ultramodern:check, testy, typecheck, lint a build.",
17
+ "title": "Ultramodern kontroly"
18
+ },
19
+ "guide": {
20
+ "body": "Zacni jednou aplikaci a pridej routy, API nebo balicky az kdyz pomuzou.",
21
+ "title": "UltraModern.js pruvodce"
22
+ }
23
+ },
24
+ "description": {
25
+ "afterConfig": "podle rustu aplikace a udrzuj",
26
+ "afterPreset": "profilem. Lad",
27
+ "end": "zelene pri pridavani rout, API a workspace balicku.",
28
+ "intro": "Zacni s verejnym"
29
+ },
30
+ "language": {
31
+ "cs": "Cestina",
32
+ "en": "Anglictina",
33
+ "switcher": "Jazyk"
34
+ },
35
+ "logoAlt": "Logo UltraModern.js",
36
+ "meta": {
37
+ "description": "Lokalizovany starter aplikace UltraModern.js se silnymi vychozimi hodnotami pro komplexni produkty.",
38
+ "title": "UltraModern.js Starter"
39
+ },
40
+ "name": "Jednoduchy starter aplikace",
41
+ "skipLink": "Preskocit na obsah",
42
+ "title": "UltraModern.js Starter"
43
+ }
44
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "home": {
3
+ "bff": {
4
+ "response": "Effect HttpApi response:"
5
+ },
6
+ "cards": {
7
+ "bff": {
8
+ "body": "Add server logic when a route needs typed data or mutation handling.",
9
+ "title": "BFF + Effect"
10
+ },
11
+ "config": {
12
+ "body": "Tune generated defaults in modern.config.ts as the app grows.",
13
+ "title": "Configure presetUltramodern"
14
+ },
15
+ "gates": {
16
+ "body": "Run ultramodern:check, tests, typecheck, lint, and build before release.",
17
+ "title": "Ultramodern Gates"
18
+ },
19
+ "guide": {
20
+ "body": "Start with one app, then add routes, APIs, and packages when they help.",
21
+ "title": "UltraModern.js Guide"
22
+ }
23
+ },
24
+ "description": {
25
+ "afterConfig": "as the app grows, and keep",
26
+ "afterPreset": "profile. Tune",
27
+ "end": "green while you add routes, APIs, and workspace packages.",
28
+ "intro": "Start from the public"
29
+ },
30
+ "language": {
31
+ "cs": "Czech",
32
+ "en": "English",
33
+ "switcher": "Language"
34
+ },
35
+ "logoAlt": "UltraModern.js Logo",
36
+ "meta": {
37
+ "description": "A localized UltraModern.js app starter with strong defaults for complex products.",
38
+ "title": "UltraModern.js Starter"
39
+ },
40
+ "name": "Simple app starter",
41
+ "skipLink": "Skip to content",
42
+ "title": "UltraModern.js Starter"
43
+ }
44
+ }
@@ -0,0 +1,10 @@
1
+ pre-commit:
2
+ commands:
3
+ fix-and-check:
4
+ run: pnpm format && pnpm lint:fix && pnpm ultramodern:check
5
+ stage_fixed: true
6
+
7
+ pre-push:
8
+ commands:
9
+ check:
10
+ run: pnpm ultramodern:check
@@ -2,7 +2,8 @@
2
2
  import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools';
3
3
  import path from 'node:path';
4
4
  {{#if enableBff}}import { bffPlugin } from '@modern-js/plugin-bff';
5
- {{/if}}{{#if isTanstackRouter}}import { tanstackRouterPlugin } from '@modern-js/plugin-tanstack';
5
+ {{/if}}import { i18nPlugin } from '@modern-js/plugin-i18n';
6
+ {{#if isTanstackRouter}}import { tanstackRouterPlugin } from '@modern-js/plugin-tanstack';
6
7
  {{/if}}
7
8
  const appId = process.env['MODERN_BASELINE_APP_ID'] || path.basename(process.cwd());
8
9
  const enableModuleFederationSSR = process.env['MODERN_BASELINE_ENABLE_MF_SSR'] !== 'false';
@@ -11,6 +12,18 @@ const enableTelemetryExporters =
11
12
  process.env['MODERN_BASELINE_ENABLE_TELEMETRY_EXPORTERS'] !== 'false';
12
13
  const telemetryFailLoudStartup = process.env['MODERN_TELEMETRY_FAIL_LOUD_STARTUP'] !== 'false';
13
14
  const otlpEndpoint = process.env['MODERN_TELEMETRY_OTLP_ENDPOINT'];
15
+ const configuredSiteUrl = process.env['MODERN_PUBLIC_SITE_URL'];
16
+ const hasConfiguredSiteUrl = typeof configuredSiteUrl === 'string' && configuredSiteUrl.length > 0;
17
+ const isProductionBuild =
18
+ process.env['NODE_ENV'] === 'production' || process.argv.includes('build');
19
+
20
+ if (isProductionBuild && !hasConfiguredSiteUrl) {
21
+ throw new Error(
22
+ 'MODERN_PUBLIC_SITE_URL must be set for production builds so canonical and hreflang URLs use the deployed origin.',
23
+ );
24
+ }
25
+
26
+ const siteUrl = hasConfiguredSiteUrl ? configuredSiteUrl : 'http://localhost:8080';
14
27
  const victoriaMetricsEndpoint = process.env['MODERN_TELEMETRY_VICTORIA_ENDPOINT'];
15
28
 
16
29
  // https://bleedingdev.github.io/ultramodern.js/configure/app/usage.html
@@ -19,27 +32,46 @@ export default defineConfig(
19
32
  {
20
33
  {{#if enableBff}} bff: {
21
34
  {{#if useEffectBff}} effect: {
35
+ entry: './api/effect/index',
22
36
  openapi: true,
23
37
  },
24
38
 
25
39
  {{/if}} runtimeFramework: '{{bffRuntime}}',
26
40
  },
27
41
 
28
- {{/if}} plugins: [
42
+ {{/if}} html: {
43
+ meta: {
44
+ viewport: 'width=device-width, initial-scale=1.0, viewport-fit=cover',
45
+ },
46
+ title: 'UltraModern.js Starter',
47
+ },
48
+ plugins: [
29
49
  appTools(),
50
+ i18nPlugin({
51
+ localeDetection: {
52
+ fallbackLanguage: 'en',
53
+ languages: ['en', 'cs'],
54
+ localePathRedirect: true,
55
+ },
56
+ }),
30
57
  {{#if isTanstackRouter}}
31
58
  tanstackRouterPlugin(),
32
59
  {{/if}}{{#if enableBff}}
33
60
  bffPlugin(),
34
61
  {{/if}} ],
62
+ source: {
63
+ globalVars: {
64
+ ULTRAMODERN_SITE_URL: siteUrl,
65
+ },
66
+ },
35
67
  },
36
68
  {
37
69
  appId,
38
70
  enableBffRequestId,
39
71
  enableModuleFederationSSR,
40
72
  enableTelemetryExporters,
41
- telemetryFailLoudStartup,
42
73
  ...(typeof otlpEndpoint === 'string' ? { otlpEndpoint } : {}),
74
+ telemetryFailLoudStartup,
43
75
  ...(typeof victoriaMetricsEndpoint === 'string' ? { victoriaMetricsEndpoint } : {}),
44
76
  },
45
77
  ),
@@ -3,6 +3,13 @@ import ultracite from 'ultracite/oxfmt';
3
3
 
4
4
  export default defineConfig({
5
5
  extends: [ultracite],
6
- ignorePatterns: ['dist', 'node_modules', '.modern', '.modernjs', '**/routeTree.gen.ts'],
6
+ ignorePatterns: [
7
+ '.agents',
8
+ 'dist',
9
+ 'node_modules',
10
+ '.modern',
11
+ '.modernjs',
12
+ '**/routeTree.gen.ts',
13
+ ],
7
14
  singleQuote: true,
8
15
  });
@@ -8,5 +8,12 @@ export default defineConfig({
8
8
  node: true,
9
9
  },
10
10
  extends: [core, react],
11
- ignorePatterns: ['dist', 'node_modules', '.modern', '.modernjs', '**/routeTree.gen.ts'],
11
+ ignorePatterns: [
12
+ '.agents',
13
+ 'dist',
14
+ 'node_modules',
15
+ '.modern',
16
+ '.modernjs',
17
+ '**/routeTree.gen.ts',
18
+ ],
12
19
  });
@@ -1,62 +1,69 @@
1
1
  {
2
2
  "name": "{{packageName}}",
3
3
  "version": "0.1.0",
4
+ "private": true,
4
5
  "type": "module",
6
+ "packageManager": "pnpm@{{pnpmVersion}}",
5
7
  "scripts": {
6
8
  "reset": "npx rimraf node_modules ./**/node_modules",
7
9
  "dev": "modern dev",
8
10
  "build": "modern build",
9
11
  "serve": "modern serve",
12
+ "test": "rstest run",
10
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}}
11
16
  "skills:install": "node ./scripts/bootstrap-agent-skills.mjs",
12
17
  "skills:check": "node ./scripts/bootstrap-agent-skills.mjs --check",
13
- "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": "oxfmt . && node ./scripts/bootstrap-agent-skills.mjs",
19
+ {{/unless}}
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",
14
21
  "format": "oxfmt .",
15
22
  "format:check": "oxfmt --check .",
16
23
  "lint": "oxlint .",
17
- "lint:fix": "oxlint . --fix",
18
- "prepare": "simple-git-hooks"{{/unless}}
24
+ "lint:fix": "oxlint . --fix"
19
25
  },
20
26
  "dependencies": {
27
+ "@modern-js/plugin-i18n": "{{pluginI18nVersion}}",
21
28
  {{#if isTanstackRouter}} "@modern-js/plugin-tanstack": "{{pluginTanstackVersion}}",
22
- {{/if}} "@modern-js/runtime": "{{runtimeVersion}}",
23
- {{#if isTanstackRouter}} "@tanstack/react-router": "1.170.1",
24
29
  {{/if}}
25
- "react": "^19.2.3",
26
- "react-dom": "^19.2.0"
30
+ "@modern-js/runtime": "{{runtimeVersion}}",
31
+ {{#if isTanstackRouter}} "@tanstack/react-router": "{{tanstackRouterVersion}}",
32
+ {{/if}}
33
+ "i18next": "{{i18nextVersion}}",
34
+ "react": "{{reactVersion}}",
35
+ "react-dom": "{{reactDomVersion}}",
36
+ "react-i18next": "{{reactI18nextVersion}}"
27
37
  },
28
38
  "devDependencies": {
29
- "@effect/tsgo": "0.7.3",
39
+ "@effect/tsgo": "{{effectTsgoVersion}}",
40
+ "@modern-js/adapter-rstest": "{{adapterRstestVersion}}",
30
41
  "@modern-js/app-tools": "{{appToolsVersion}}",
42
+ "@modern-js/code-tools": "{{codeToolsVersion}}",
43
+ "@modern-js/create": "{{createVersion}}",
31
44
  {{#if enableBff}} "@modern-js/plugin-bff": "{{pluginBffVersion}}",
32
45
  {{/if}} "@modern-js/tsconfig": "{{tsconfigVersion}}",
46
+ "@rstest/core": "{{rstestCoreVersion}}",
33
47
  {{#if enableTailwind}}
34
- "@tailwindcss/postcss": "^4.1.18",
48
+ "@tailwindcss/postcss": "^{{tailwindPostcssVersion}}",
35
49
  {{/if}}
36
50
  "@types/node": "^20",
37
- "@types/react": "^19.1.8",
38
- "@types/react-dom": "^19.1.6",
39
- "@typescript/native-preview": "7.0.0-dev.20260518.1",
51
+ "@types/react": "{{typesReactVersion}}",
52
+ "@types/react-dom": "{{typesReactDomVersion}}",
53
+ "@typescript/native-preview": "{{typescriptNativePreviewVersion}}",
54
+ "happy-dom": "{{happyDomVersion}}",
40
55
  {{#unless isSubproject}}
41
- "lint-staged": "~15.4.0",
42
- "oxfmt": "0.50.0",
43
- "oxlint": "1.65.0",
44
- {{/unless}}{{#if enableTailwind}} "postcss": "^8.5.6",
45
- {{/if}} "rimraf": "^6.0.1"{{#unless isSubproject}},
46
- "simple-git-hooks": "^2.11.1"{{/unless}}{{#if enableTailwind}},
47
- "tailwindcss": "^4.1.18"{{/if}}{{#unless isSubproject}},
48
- "ultracite": "7.7.0"{{/unless}}
49
- }{{#unless isSubproject}},
50
- "simple-git-hooks": {
51
- "pre-commit": "npx lint-staged"
56
+ "lefthook": "^2.1.9",
57
+ {{/unless}}
58
+ "oxfmt": "{{oxfmtVersion}}",
59
+ "oxlint": "{{oxlintVersion}}",
60
+ {{#if enableTailwind}} "postcss": "{{postcssVersion}}",
61
+ {{/if}} "rimraf": "^6.1.3"{{#if enableTailwind}},
62
+ "tailwindcss": "^{{tailwindVersion}}"{{/if}},
63
+ "ultracite": "{{ultraciteVersion}}"
52
64
  },
53
- "lint-staged": {
54
- "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
55
- "oxfmt --write",
56
- "oxlint --fix"
57
- ]
58
- }{{/unless}},
59
65
  "engines": {
60
- "node": ">=20"
66
+ "node": ">=20",
67
+ "pnpm": ">={{pnpmVersion}} <11.6.0"
61
68
  }
62
69
  }
@@ -0,0 +1,34 @@
1
+ minimumReleaseAge: 1440
2
+ minimumReleaseAgeStrict: true
3
+ minimumReleaseAgeIgnoreMissingTime: false
4
+ minimumReleaseAgeExclude:
5
+ - '@bleedingdev/modern-js-*'
6
+ - '@tanstack/react-router'
7
+ - '@tanstack/router-core'
8
+ - '@typescript/native-preview'
9
+ - '@typescript/native-preview-*'
10
+ - '@types/react'
11
+ trustPolicy: no-downgrade
12
+ trustPolicyIgnoreAfter: 1440
13
+ blockExoticSubdeps: true
14
+ engineStrict: true
15
+ pmOnFail: error
16
+ verifyDepsBeforeRun: error
17
+ strictDepBuilds: true
18
+
19
+ allowBuilds:
20
+ '@swc/core': true
21
+ core-js: true
22
+ esbuild: true
23
+ lefthook: true
24
+ msgpackr-extract: true
25
+ sharp: true
26
+ workerd: true
27
+ onlyBuiltDependencies:
28
+ - '@swc/core'
29
+ - core-js
30
+ - esbuild
31
+ - lefthook
32
+ - msgpackr-extract
33
+ - sharp
34
+ - workerd
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from '@rstest/core';
2
+
3
+ export default defineConfig({
4
+ testEnvironment: 'node',
5
+ });
@@ -17,16 +17,113 @@ const run = (command, args, options = {}) =>
17
17
  stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
18
18
  });
19
19
 
20
+ const commandExists = (command) => {
21
+ try {
22
+ run(command, ['--version'], { stdio: 'ignore' });
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ };
28
+
29
+ const runShell = (script) =>
30
+ run('sh', ['-lc', script], {
31
+ stdio: 'inherit',
32
+ });
33
+
34
+ const installGit = () => {
35
+ if (commandExists('git')) {
36
+ return;
37
+ }
38
+
39
+ if (commandExists('brew')) {
40
+ run('brew', ['install', 'git'], { stdio: 'inherit' });
41
+ } else if (process.platform === 'linux' && commandExists('apt-get')) {
42
+ const sudo = typeof process.getuid === 'function' && process.getuid() === 0 ? '' : 'sudo ';
43
+ runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
44
+ } else if (process.platform === 'linux' && commandExists('dnf')) {
45
+ const sudo = typeof process.getuid === 'function' && process.getuid() === 0 ? '' : 'sudo ';
46
+ runShell(`${sudo}dnf install -y git`);
47
+ } else if (process.platform === 'linux' && commandExists('yum')) {
48
+ const sudo = typeof process.getuid === 'function' && process.getuid() === 0 ? '' : 'sudo ';
49
+ runShell(`${sudo}yum install -y git`);
50
+ } else if (process.platform === 'linux' && commandExists('apk')) {
51
+ runShell('apk add --no-cache git');
52
+ }
53
+
54
+ if (!commandExists('git')) {
55
+ throw new Error(
56
+ 'Git is required for UltraModern setup. Install git and run pnpm skills:install again.',
57
+ );
58
+ }
59
+ };
60
+
61
+ const isInsideGitWorkTree = () => {
62
+ try {
63
+ return run('git', ['rev-parse', '--is-inside-work-tree']).trim() === 'true';
64
+ } catch {
65
+ return false;
66
+ }
67
+ };
68
+
69
+ const initializeGitRepository = () => {
70
+ if (isInsideGitWorkTree()) {
71
+ return;
72
+ }
73
+
74
+ try {
75
+ run('git', ['init', '-b', 'main'], { stdio: 'inherit' });
76
+ } catch {
77
+ run('git', ['init'], { stdio: 'inherit' });
78
+ run('git', ['branch', '-M', 'main'], { stdio: 'inherit' });
79
+ }
80
+ };
81
+
82
+ const installLefthook = () => {
83
+ try {
84
+ run('lefthook', ['install'], { stdio: 'inherit' });
85
+ } catch (error) {
86
+ console.warn(`Unable to install lefthook hooks: ${error.message}`);
87
+ }
88
+ };
89
+
90
+ const removeTree = (dir) =>
91
+ fs.rmSync(dir, {
92
+ force: true,
93
+ maxRetries: 5,
94
+ recursive: true,
95
+ retryDelay: 100,
96
+ });
97
+
20
98
  const cloneSource = (source, targetDir) => {
99
+ if (source.commit) {
100
+ run('git', ['init', targetDir]);
101
+ run('git', ['remote', 'add', 'origin', source.repository], {
102
+ cwd: targetDir,
103
+ });
104
+ run('git', ['fetch', '--depth', '1', '--quiet', 'origin', source.commit], {
105
+ cwd: targetDir,
106
+ });
107
+ run(
108
+ 'git',
109
+ [
110
+ '-c',
111
+ 'advice.detachedHead=false',
112
+ 'checkout',
113
+ '--detach',
114
+ '--quiet',
115
+ 'FETCH_HEAD',
116
+ ],
117
+ { cwd: targetDir },
118
+ );
119
+ return;
120
+ }
121
+
21
122
  const repo = source.repository.replace(/^https:\/\/github.com\//u, '');
22
123
  try {
23
- run('gh', ['repo', 'clone', repo, targetDir, '--', '--depth', '1'], {
24
- stdio: 'inherit',
25
- });
124
+ run('gh', ['repo', 'clone', repo, targetDir, '--', '--depth', '1', '--quiet']);
26
125
  } catch {
27
- run('git', ['clone', '--depth', '1', source.repository, targetDir], {
28
- stdio: 'inherit',
29
- });
126
+ run('git', ['clone', '--depth', '1', '--quiet', source.repository, targetDir]);
30
127
  }
31
128
  };
32
129
 
@@ -47,32 +144,65 @@ if (!fs.existsSync(lockPath)) {
47
144
 
48
145
  const lock = readJson(lockPath);
49
146
  const installDir = path.join(root, lock.installDir ?? '.agents/skills');
50
- const privateSources = (lock.sources ?? []).filter(
147
+ const sources = lock.sources ?? [];
148
+ const requiredCloneSources = sources.filter((source) => source.install === 'clone');
149
+ const optionalCloneSources = sources.filter(
51
150
  (source) => source.install === 'clone-if-authorized',
52
151
  );
152
+ const requiredSkills = [
153
+ ...(lock.baseline ?? []),
154
+ ...requiredCloneSources.flatMap((source) => source.baseline ?? []),
155
+ ].filter(
156
+ (skill, index, skills) =>
157
+ skills.findIndex((candidate) => candidate.name === skill.name) === index,
158
+ );
53
159
 
54
160
  if (checkOnly) {
55
- const missing = privateSources.flatMap((source) =>
161
+ const missingRequired = requiredSkills
162
+ .map((skill) => skill.name)
163
+ .filter((skillName) => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')));
164
+ const missingOptional = optionalCloneSources.flatMap((source) =>
56
165
  (source.baseline ?? [])
57
166
  .map((skill) => skill.name)
58
167
  .filter((skillName) => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md'))),
59
168
  );
60
- if (missing.length > 0) {
169
+
170
+ if (missingRequired.length > 0) {
171
+ console.error(
172
+ `Required agent skills not installed: ${missingRequired.join(', ')}. Run pnpm skills:install.`,
173
+ );
174
+ process.exit(1);
175
+ }
176
+
177
+ if (missingOptional.length > 0) {
61
178
  console.warn(
62
- `Private skills not installed: ${missing.join(', ')}. Run pnpm skills:install if you have access.`,
179
+ `Private skills not installed: ${missingOptional.join(', ')}. Run pnpm skills:install if you have access.`,
63
180
  );
64
181
  } else {
65
- console.log('Agent skills are installed.');
182
+ console.log('Required and private agent skills are installed.');
183
+ process.exit(0);
66
184
  }
185
+ console.log('Required agent skills are installed.');
67
186
  process.exit(0);
68
187
  }
69
188
 
70
189
  fs.mkdirSync(installDir, { recursive: true });
190
+ installGit();
71
191
 
72
- for (const source of privateSources) {
192
+ for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
73
193
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ultramodern-skills-'));
74
194
  try {
75
- cloneSource(source, tempDir);
195
+ try {
196
+ cloneSource(source, tempDir);
197
+ } catch (error) {
198
+ if (source.install === 'clone-if-authorized') {
199
+ console.warn(
200
+ `Skipping ${source.repository}; current developer may not have access.`,
201
+ );
202
+ continue;
203
+ }
204
+ throw error;
205
+ }
76
206
  for (const skill of source.baseline ?? []) {
77
207
  const sourceSkillDir = resolveSkillDir(tempDir, skill.name);
78
208
  if (!sourceSkillDir) {
@@ -84,12 +214,15 @@ for (const source of privateSources) {
84
214
  console.log(`Skipping existing ${skill.name}`);
85
215
  continue;
86
216
  }
87
- fs.rmSync(targetSkillDir, { force: true, recursive: true });
217
+ removeTree(targetSkillDir);
88
218
  }
89
219
  fs.cpSync(sourceSkillDir, targetSkillDir, { recursive: true });
90
220
  console.log(`Installed ${skill.name}`);
91
221
  }
92
222
  } finally {
93
- fs.rmSync(tempDir, { force: true, recursive: true });
223
+ removeTree(tempDir);
94
224
  }
95
225
  }
226
+
227
+ initializeGitRepository();
228
+ installLefthook();
@@ -0,0 +1,3 @@
1
+ import { runSingleAppI18nCheck } from '@modern-js/code-tools';
2
+
3
+ process.exitCode = runSingleAppI18nCheck();