@bleedingdev/modern-js-create 3.2.0-ultramodern.6 → 3.2.0-ultramodern.61
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 +116 -22
- package/dist/index.js +4563 -603
- package/dist/types/locale/en.d.ts +3 -0
- package/dist/types/locale/zh.d.ts +3 -0
- package/dist/types/ultramodern-workspace.d.ts +11 -0
- package/package.json +6 -6
- package/template/.agents/skills-lock.json +34 -0
- package/template/.codex/hooks.json +16 -0
- package/template/.github/renovate.json +53 -0
- package/template/.github/workflows/ultramodern-gates.yml.handlebars +34 -10
- package/template/.mise.toml.handlebars +2 -0
- package/template/AGENTS.md +23 -0
- package/template/README.md +60 -34
- package/template/api/effect/index.ts.handlebars +7 -45
- package/template/config/public/locales/cs/translation.json +39 -0
- package/template/config/public/locales/en/translation.json +39 -0
- package/template/lefthook.yml +15 -0
- package/template/modern.config.ts.handlebars +44 -23
- package/template/oxfmt.config.ts +8 -0
- package/template/oxlint.config.ts +12 -0
- package/template/package.json.handlebars +50 -31
- package/template/pnpm-workspace.yaml +19 -0
- package/template/rstest.config.mts +7 -0
- package/template/scripts/bootstrap-agent-skills.mjs +135 -0
- package/template/scripts/check-i18n-strings.mjs +83 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +439 -17
- package/template/shared/effect/api.ts.handlebars +1 -2
- package/template/src/modern-app-env.d.ts +2 -0
- package/template/src/modern.runtime.ts.handlebars +17 -3
- package/template/src/routes/[lang]/page.tsx.handlebars +212 -0
- package/template/src/routes/index.css.handlebars +14 -3
- package/template/src/routes/layout.tsx.handlebars +2 -1
- package/template/tests/tsconfig.json +7 -0
- package/template/tests/ultramodern.contract.test.ts.handlebars +78 -0
- package/template/tsconfig.json +106 -2
- package/template-workspace/.agents/agent-reference-repos.json +24 -0
- package/template-workspace/.agents/rstackjs-agent-skills-LICENSE +21 -0
- package/template-workspace/.agents/skills/rsbuild-best-practices/SKILL.md +57 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/SKILL.md +96 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/command-map.md +113 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/common-analysis-patterns.md +190 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-common.md +88 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-rspack.md +138 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor-webpack.md +71 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/install-rsdoctor.md +39 -0
- package/template-workspace/.agents/skills/rsdoctor-analysis/references/rsdoctor-data-types.md +103 -0
- package/template-workspace/.agents/skills/rslib-best-practices/SKILL.md +58 -0
- package/template-workspace/.agents/skills/rslib-modern-package/SKILL.md +173 -0
- package/template-workspace/.agents/skills/rspack-best-practices/SKILL.md +70 -0
- package/template-workspace/.agents/skills/rspack-tracing/SKILL.md +75 -0
- package/template-workspace/.agents/skills/rspack-tracing/references/bottlenecks.md +47 -0
- package/template-workspace/.agents/skills/rspack-tracing/references/tracing-guide.md +38 -0
- package/template-workspace/.agents/skills/rspack-tracing/scripts/analyze_trace.js +184 -0
- package/template-workspace/.agents/skills/rstest-best-practices/SKILL.md +133 -0
- package/template-workspace/.agents/skills-lock.json +114 -0
- package/template-workspace/.codex/hooks.json +16 -0
- package/template-workspace/.github/renovate.json +29 -0
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +54 -0
- package/template-workspace/.gitignore.handlebars +5 -0
- package/template-workspace/.mise.toml.handlebars +2 -0
- package/template-workspace/AGENTS.md +76 -0
- package/template-workspace/README.md.handlebars +33 -10
- package/template-workspace/lefthook.yml +15 -0
- package/template-workspace/oxfmt.config.ts +16 -0
- package/template-workspace/oxlint.config.ts +19 -0
- package/template-workspace/pnpm-workspace.yaml +20 -10
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +163 -0
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +368 -0
- package/template/biome.json +0 -41
- package/template/src/routes/page.tsx.handlebars +0 -119
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -276
|
@@ -1,41 +1,62 @@
|
|
|
1
|
+
// @effect-diagnostics nodeBuiltinImport:off processEnv:off
|
|
1
2
|
import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
{{#if enableBff}}import { bffPlugin } from '@modern-js/plugin-bff';
|
|
4
|
-
{{/if}}
|
|
5
|
+
{{/if}}import { i18nPlugin } from '@modern-js/plugin-i18n';
|
|
6
|
+
{{#if isTanstackRouter}}import { tanstackRouterPlugin } from '@modern-js/plugin-tanstack';
|
|
5
7
|
{{/if}}
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
process.env.MODERN_BASELINE_ENABLE_MF_SSR !== 'false';
|
|
10
|
-
const enableBffRequestId =
|
|
11
|
-
process.env.MODERN_BASELINE_ENABLE_BFF_REQUEST_ID !== 'false';
|
|
8
|
+
const appId = process.env['MODERN_BASELINE_APP_ID'] || path.basename(process.cwd());
|
|
9
|
+
const enableModuleFederationSSR = process.env['MODERN_BASELINE_ENABLE_MF_SSR'] !== 'false';
|
|
10
|
+
const enableBffRequestId = process.env['MODERN_BASELINE_ENABLE_BFF_REQUEST_ID'] !== 'false';
|
|
12
11
|
const enableTelemetryExporters =
|
|
13
|
-
process.env
|
|
14
|
-
const telemetryFailLoudStartup =
|
|
15
|
-
|
|
12
|
+
process.env['MODERN_BASELINE_ENABLE_TELEMETRY_EXPORTERS'] !== 'false';
|
|
13
|
+
const telemetryFailLoudStartup = process.env['MODERN_TELEMETRY_FAIL_LOUD_STARTUP'] !== 'false';
|
|
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';
|
|
27
|
+
const victoriaMetricsEndpoint = process.env['MODERN_TELEMETRY_VICTORIA_ENDPOINT'];
|
|
16
28
|
|
|
17
29
|
// https://bleedingdev.github.io/ultramodern.js/configure/app/usage.html
|
|
18
30
|
export default defineConfig(
|
|
19
31
|
presetUltramodern(
|
|
20
32
|
{
|
|
21
|
-
|
|
33
|
+
{{#if enableBff}} bff: {
|
|
34
|
+
{{#if useEffectBff}} effect: {
|
|
35
|
+
openapi: true,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
{{/if}} runtimeFramework: '{{bffRuntime}}',
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
{{/if}} plugins: [
|
|
22
42
|
appTools(),
|
|
43
|
+
i18nPlugin({
|
|
44
|
+
localeDetection: {
|
|
45
|
+
fallbackLanguage: 'en',
|
|
46
|
+
languages: ['en', 'cs'],
|
|
47
|
+
localePathRedirect: true,
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
23
50
|
{{#if isTanstackRouter}}
|
|
24
51
|
tanstackRouterPlugin(),
|
|
25
52
|
{{/if}}{{#if enableBff}}
|
|
26
53
|
bffPlugin(),
|
|
27
|
-
{{/if}}
|
|
28
|
-
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
runtimeFramework: '{{bffRuntime}}',
|
|
32
|
-
{{#if useEffectBff}}
|
|
33
|
-
effect: {
|
|
34
|
-
openapi: true,
|
|
54
|
+
{{/if}} ],
|
|
55
|
+
source: {
|
|
56
|
+
globalVars: {
|
|
57
|
+
ULTRAMODERN_SITE_URL: siteUrl,
|
|
35
58
|
},
|
|
36
|
-
{{/if}}
|
|
37
59
|
},
|
|
38
|
-
{{/if}}
|
|
39
60
|
},
|
|
40
61
|
{
|
|
41
62
|
appId,
|
|
@@ -43,8 +64,8 @@ export default defineConfig(
|
|
|
43
64
|
enableModuleFederationSSR,
|
|
44
65
|
enableTelemetryExporters,
|
|
45
66
|
telemetryFailLoudStartup,
|
|
46
|
-
otlpEndpoint:
|
|
47
|
-
victoriaMetricsEndpoint:
|
|
67
|
+
...(typeof otlpEndpoint === 'string' ? { otlpEndpoint } : {}),
|
|
68
|
+
...(typeof victoriaMetricsEndpoint === 'string' ? { victoriaMetricsEndpoint } : {}),
|
|
48
69
|
},
|
|
49
70
|
),
|
|
50
71
|
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'oxlint';
|
|
2
|
+
import core from 'ultracite/oxlint/core';
|
|
3
|
+
import react from 'ultracite/oxlint/react';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
env: {
|
|
7
|
+
browser: true,
|
|
8
|
+
node: true,
|
|
9
|
+
},
|
|
10
|
+
extends: [core, react],
|
|
11
|
+
ignorePatterns: ['dist', 'node_modules', '.modern', '.modernjs', '**/routeTree.gen.ts'],
|
|
12
|
+
});
|
|
@@ -1,47 +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",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
+
"test": "rstest run",
|
|
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}}
|
|
16
|
+
"skills:install": "node ./scripts/bootstrap-agent-skills.mjs",
|
|
17
|
+
"skills:check": "node ./scripts/bootstrap-agent-skills.mjs --check",
|
|
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}},
|
|
21
|
+
"format": "oxfmt .",
|
|
22
|
+
"format:check": "oxfmt --check .",
|
|
23
|
+
"lint": "oxlint .",
|
|
24
|
+
"lint:fix": "oxlint . --fix"{{/unless}}
|
|
12
25
|
},
|
|
13
|
-
"engines": {
|
|
14
|
-
"node": ">=20"
|
|
15
|
-
}{{#unless isSubproject}},
|
|
16
|
-
"lint-staged": {
|
|
17
|
-
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [
|
|
18
|
-
"biome check --files-ignore-unknown=true"
|
|
19
|
-
]
|
|
20
|
-
},
|
|
21
|
-
"simple-git-hooks": {
|
|
22
|
-
"pre-commit": "npx lint-staged"
|
|
23
|
-
}{{/unless}},
|
|
24
26
|
"dependencies": {
|
|
25
|
-
"@modern-js/
|
|
26
|
-
"@modern-js/plugin-tanstack": "{{
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"react-
|
|
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"
|
|
30
37
|
},
|
|
31
38
|
"devDependencies": {
|
|
32
|
-
"@
|
|
33
|
-
"@modern-js/
|
|
34
|
-
"@modern-js/
|
|
35
|
-
"@
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
"@
|
|
39
|
+
"@effect/tsgo": "0.13.0",
|
|
40
|
+
"@modern-js/adapter-rstest": "{{adapterRstestVersion}}",
|
|
41
|
+
"@modern-js/app-tools": "{{appToolsVersion}}",
|
|
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}}
|
|
40
48
|
"@types/node": "^20",
|
|
41
49
|
"@types/react": "^19.1.8",
|
|
42
|
-
"@types/react-dom": "^19.1.6"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
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"
|
|
46
65
|
}
|
|
47
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,135 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const root = process.cwd();
|
|
7
|
+
const lockPath = path.join(root, '.agents/skills-lock.json');
|
|
8
|
+
const checkOnly = process.argv.includes('--check');
|
|
9
|
+
const force = process.argv.includes('--force');
|
|
10
|
+
|
|
11
|
+
const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
12
|
+
|
|
13
|
+
const run = (command, args, options = {}) =>
|
|
14
|
+
execFileSync(command, args, {
|
|
15
|
+
cwd: options.cwd ?? root,
|
|
16
|
+
encoding: 'utf-8',
|
|
17
|
+
stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
|
|
18
|
+
});
|
|
19
|
+
|
|
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, '');
|
|
30
|
+
try {
|
|
31
|
+
run('gh', ['repo', 'clone', repo, targetDir, '--', '--depth', '1'], {
|
|
32
|
+
stdio: 'inherit',
|
|
33
|
+
});
|
|
34
|
+
} catch {
|
|
35
|
+
run('git', ['clone', '--depth', '1', source.repository, targetDir], {
|
|
36
|
+
stdio: 'inherit',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const resolveSkillDir = (sourceRoot, skillName) => {
|
|
42
|
+
const candidates = [
|
|
43
|
+
path.join(sourceRoot, skillName),
|
|
44
|
+
path.join(sourceRoot, 'skills', skillName),
|
|
45
|
+
path.join(sourceRoot, 'skills', 'engineering', skillName),
|
|
46
|
+
path.join(sourceRoot, 'skills', 'productivity', skillName),
|
|
47
|
+
];
|
|
48
|
+
return candidates.find((candidate) => fs.existsSync(path.join(candidate, 'SKILL.md')));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (!fs.existsSync(lockPath)) {
|
|
52
|
+
console.error('Missing .agents/skills-lock.json');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const lock = readJson(lockPath);
|
|
57
|
+
const installDir = path.join(root, lock.installDir ?? '.agents/skills');
|
|
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,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (checkOnly) {
|
|
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) =>
|
|
76
|
+
(source.baseline ?? [])
|
|
77
|
+
.map((skill) => skill.name)
|
|
78
|
+
.filter((skillName) => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md'))),
|
|
79
|
+
);
|
|
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) {
|
|
89
|
+
console.warn(
|
|
90
|
+
`Private skills not installed: ${missingOptional.join(', ')}. Run pnpm skills:install if you have access.`,
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
console.log('Required and private agent skills are installed.');
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
console.log('Required agent skills are installed.');
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
101
|
+
|
|
102
|
+
for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
|
|
103
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ultramodern-skills-'));
|
|
104
|
+
try {
|
|
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
|
+
}
|
|
116
|
+
for (const skill of source.baseline ?? []) {
|
|
117
|
+
const sourceSkillDir = resolveSkillDir(tempDir, skill.name);
|
|
118
|
+
if (!sourceSkillDir) {
|
|
119
|
+
throw new Error(`Skill ${skill.name} not found in ${source.repository}`);
|
|
120
|
+
}
|
|
121
|
+
const targetSkillDir = path.join(installDir, skill.name);
|
|
122
|
+
if (fs.existsSync(targetSkillDir)) {
|
|
123
|
+
if (!force) {
|
|
124
|
+
console.log(`Skipping existing ${skill.name}`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
removeTree(targetSkillDir);
|
|
128
|
+
}
|
|
129
|
+
fs.cpSync(sourceSkillDir, targetSkillDir, { recursive: true });
|
|
130
|
+
console.log(`Installed ${skill.name}`);
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
removeTree(tempDir);
|
|
134
|
+
}
|
|
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.');
|