@bleedingdev/modern-js-create 3.2.0-ultramodern.17 → 3.2.0-ultramodern.19
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 +21 -2
- package/package.json +1 -1
- package/template/.github/renovate.json +53 -0
- package/template/.github/workflows/ultramodern-gates.yml.handlebars +26 -4
- package/template/README.md +7 -3
- package/template/scripts/validate-ultramodern.mjs.handlebars +49 -0
- package/template-workspace/.agents/agent-reference-repos.json +23 -0
- package/template-workspace/.github/renovate.json +29 -0
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +52 -0
- package/template-workspace/AGENTS.md +9 -0
- package/template-workspace/README.md.handlebars +11 -1
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +217 -0
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +104 -0
package/dist/index.js
CHANGED
|
@@ -556,6 +556,7 @@ const ultramodern_workspace_dirname = node_path.dirname(fileURLToPath(import.met
|
|
|
556
556
|
const workspaceTemplateDir = node_path.resolve(ultramodern_workspace_dirname, '..', 'template-workspace');
|
|
557
557
|
const TANSTACK_ROUTER_VERSION = '1.170.1';
|
|
558
558
|
const MODULE_FEDERATION_VERSION = '2.4.0';
|
|
559
|
+
const ZEPHYR_MODERNJS_PLUGIN_VERSION = '1.1.1';
|
|
559
560
|
const EFFECT_TSGO_VERSION = '0.7.3';
|
|
560
561
|
const TYPESCRIPT_NATIVE_PREVIEW_VERSION = '7.0.0-dev.20260518.1';
|
|
561
562
|
const OXLINT_VERSION = '1.65.0';
|
|
@@ -926,6 +927,7 @@ function appDependencies(scope, packageSource) {
|
|
|
926
927
|
'@module-federation/modern-js-v3': MODULE_FEDERATION_VERSION,
|
|
927
928
|
'@module-federation/runtime': MODULE_FEDERATION_VERSION,
|
|
928
929
|
'@tanstack/react-router': TANSTACK_ROUTER_VERSION,
|
|
930
|
+
'zephyr-modernjs-plugin': ZEPHYR_MODERNJS_PLUGIN_VERSION,
|
|
929
931
|
[ultramodern_workspace_packageName(scope, 'shared-contracts')]: WORKSPACE_PACKAGE_VERSION,
|
|
930
932
|
[ultramodern_workspace_packageName(scope, 'shared-design-tokens')]: WORKSPACE_PACKAGE_VERSION,
|
|
931
933
|
i18next: I18NEXT_VERSION,
|
|
@@ -967,7 +969,10 @@ function createRootPackageJson(scope, packageSource) {
|
|
|
967
969
|
typecheck: `pnpm -r --filter "@${scope}/*" typecheck`,
|
|
968
970
|
'skills:install': "node ./scripts/bootstrap-agent-skills.mjs",
|
|
969
971
|
'skills:check': "node ./scripts/bootstrap-agent-skills.mjs --check",
|
|
972
|
+
'agents:refs:install': "node ./scripts/setup-agent-reference-repos.mjs",
|
|
973
|
+
'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mjs --check",
|
|
970
974
|
'ultramodern:check': "node ./scripts/validate-ultramodern-workspace.mjs",
|
|
975
|
+
postinstall: "node ./scripts/setup-agent-reference-repos.mjs",
|
|
971
976
|
check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm i18n:check && pnpm skills:check && pnpm ultramodern:check'
|
|
972
977
|
},
|
|
973
978
|
engines: {
|
|
@@ -1136,6 +1141,7 @@ import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools'
|
|
|
1136
1141
|
import { i18nPlugin } from '@modern-js/plugin-i18n';
|
|
1137
1142
|
import { tanstackRouterPlugin } from '@modern-js/plugin-tanstack';
|
|
1138
1143
|
import { moduleFederationPlugin } from '@module-federation/modern-js-v3';
|
|
1144
|
+
import { withZephyr } from 'zephyr-modernjs-plugin';
|
|
1139
1145
|
|
|
1140
1146
|
const appId = '${app.id}';
|
|
1141
1147
|
const port = Number(process.env['${app.portEnv}'] ?? ${app.port});
|
|
@@ -1157,11 +1163,19 @@ export default defineConfig(
|
|
|
1157
1163
|
{
|
|
1158
1164
|
output: {
|
|
1159
1165
|
disableTsChecker: true,
|
|
1166
|
+
distPath: {
|
|
1167
|
+
html: './',
|
|
1168
|
+
},
|
|
1160
1169
|
polyfill: 'off',
|
|
1161
1170
|
splitRouteChunks: false,
|
|
1162
1171
|
},
|
|
1172
|
+
html: {
|
|
1173
|
+
outputStructure: 'flat',
|
|
1174
|
+
},
|
|
1163
1175
|
plugins: [
|
|
1164
|
-
appTools(
|
|
1176
|
+
appTools({
|
|
1177
|
+
bundler: 'rspack',
|
|
1178
|
+
}),
|
|
1165
1179
|
i18nPlugin({
|
|
1166
1180
|
localeDetection: {
|
|
1167
1181
|
fallbackLanguage: 'en',
|
|
@@ -1171,6 +1185,7 @@ export default defineConfig(
|
|
|
1171
1185
|
}),
|
|
1172
1186
|
tanstackRouterPlugin(),
|
|
1173
1187
|
moduleFederationPlugin(),
|
|
1188
|
+
withZephyr(),
|
|
1174
1189
|
],
|
|
1175
1190
|
server: {
|
|
1176
1191
|
port,
|
|
@@ -1180,6 +1195,7 @@ export default defineConfig(
|
|
|
1180
1195
|
},
|
|
1181
1196
|
},
|
|
1182
1197
|
source: {
|
|
1198
|
+
mainEntryName: 'index',
|
|
1183
1199
|
globalVars: {
|
|
1184
1200
|
ULTRAMODERN_SITE_URL: siteUrl,
|
|
1185
1201
|
},
|
|
@@ -1855,6 +1871,8 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
1855
1871
|
targetRoot: 'generated-project-root',
|
|
1856
1872
|
allowedPaths: [
|
|
1857
1873
|
'.agents/**',
|
|
1874
|
+
'.github/**',
|
|
1875
|
+
'.gitignore',
|
|
1858
1876
|
'.modernjs/**',
|
|
1859
1877
|
'AGENTS.md',
|
|
1860
1878
|
'README.md',
|
|
@@ -1871,7 +1889,6 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
1871
1889
|
],
|
|
1872
1890
|
deniedPaths: [
|
|
1873
1891
|
'.git/**',
|
|
1874
|
-
'.github/**',
|
|
1875
1892
|
'.npmrc',
|
|
1876
1893
|
'.yarnrc',
|
|
1877
1894
|
'.env',
|
|
@@ -1919,6 +1936,7 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
1919
1936
|
],
|
|
1920
1937
|
postMaterializationValidation: [
|
|
1921
1938
|
'ultramodern-workspace-contract-check',
|
|
1939
|
+
'github-workflow-security-enforced',
|
|
1922
1940
|
'pnpm-11-policy-enforced',
|
|
1923
1941
|
'template-manifest-retained'
|
|
1924
1942
|
],
|
|
@@ -2259,6 +2277,7 @@ function createBuiltinTemplateManifest(version) {
|
|
|
2259
2277
|
postMaterializationValidation: [
|
|
2260
2278
|
'ultramodern-contract-check',
|
|
2261
2279
|
'dependency-install-with-lifecycle-deny',
|
|
2280
|
+
'github-workflow-security-enforced',
|
|
2262
2281
|
'package-source-retained',
|
|
2263
2282
|
'pnpm-11-policy-enforced',
|
|
2264
2283
|
'rstest-smoke-tests',
|
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
+
"extends": [
|
|
4
|
+
"config:recommended",
|
|
5
|
+
"helpers:pinGitHubActionDigests"
|
|
6
|
+
],
|
|
7
|
+
"dependencyDashboard": true,
|
|
8
|
+
"minimumReleaseAge": "1 day",
|
|
9
|
+
"prConcurrentLimit": 5,
|
|
10
|
+
"prHourlyLimit": 2,
|
|
11
|
+
"rangeStrategy": "bump",
|
|
12
|
+
"schedule": [
|
|
13
|
+
"before 5am on monday"
|
|
14
|
+
],
|
|
15
|
+
"timezone": "Etc/UTC",
|
|
16
|
+
"packageRules": [
|
|
17
|
+
{
|
|
18
|
+
"matchManagers": [
|
|
19
|
+
"github-actions"
|
|
20
|
+
],
|
|
21
|
+
"groupName": "github-actions",
|
|
22
|
+
"labels": [
|
|
23
|
+
"dependencies",
|
|
24
|
+
"github-actions",
|
|
25
|
+
"security"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"matchManagers": [
|
|
30
|
+
"npm"
|
|
31
|
+
],
|
|
32
|
+
"matchUpdateTypes": [
|
|
33
|
+
"patch",
|
|
34
|
+
"minor"
|
|
35
|
+
],
|
|
36
|
+
"groupName": "npm minor and patch updates",
|
|
37
|
+
"labels": [
|
|
38
|
+
"dependencies",
|
|
39
|
+
"npm"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"matchUpdateTypes": [
|
|
44
|
+
"major"
|
|
45
|
+
],
|
|
46
|
+
"dependencyDashboardApproval": true,
|
|
47
|
+
"labels": [
|
|
48
|
+
"dependencies",
|
|
49
|
+
"major"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
@@ -4,27 +4,49 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
pull_request:
|
|
6
6
|
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
defaults:
|
|
11
|
+
run:
|
|
12
|
+
shell: bash
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ultramodern-gates-${{ github.workflow }}-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
7
18
|
jobs:
|
|
8
19
|
ultramodern-gates:
|
|
9
20
|
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 20
|
|
10
22
|
steps:
|
|
23
|
+
- name: Harden Runner
|
|
24
|
+
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
|
|
25
|
+
with:
|
|
26
|
+
egress-policy: audit
|
|
27
|
+
|
|
11
28
|
- name: Checkout
|
|
12
|
-
uses: actions/checkout@v4
|
|
29
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 1
|
|
32
|
+
persist-credentials: false
|
|
13
33
|
|
|
14
34
|
- name: Setup pnpm
|
|
15
|
-
uses: pnpm/action-setup@v4
|
|
35
|
+
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
|
|
16
36
|
|
|
17
37
|
- name: Setup Node.js
|
|
18
|
-
uses: actions/setup-node@v4
|
|
38
|
+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
19
39
|
with:
|
|
20
40
|
node-version: 20
|
|
21
41
|
cache: pnpm
|
|
22
42
|
|
|
23
43
|
- name: Install Dependencies
|
|
24
|
-
run: pnpm install
|
|
44
|
+
run: pnpm install --frozen-lockfile
|
|
25
45
|
|
|
26
46
|
- name: Validate Ultramodern Contract
|
|
27
47
|
run: pnpm run ultramodern:check
|
|
28
48
|
|
|
29
49
|
- name: Build
|
|
50
|
+
env:
|
|
51
|
+
MODERN_PUBLIC_SITE_URL: http://localhost:8080
|
|
30
52
|
run: pnpm run build
|
package/template/README.md
CHANGED
|
@@ -36,9 +36,13 @@ MODERN_BASELINE_ENABLE_BFF_REQUEST_ID=false
|
|
|
36
36
|
MODERN_BASELINE_ENABLE_TELEMETRY_EXPORTERS=false
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
The generated starter also includes `.github/workflows/ultramodern-gates.yml
|
|
40
|
-
|
|
41
|
-
push and pull request
|
|
39
|
+
The generated starter also includes `.github/workflows/ultramodern-gates.yml`
|
|
40
|
+
and `.github/renovate.json`. The workflow runs `pnpm run ultramodern:check` and
|
|
41
|
+
`pnpm run build` on every push and pull request with read-only permissions,
|
|
42
|
+
commit-pinned actions, frozen installs, and StepSecurity audit-mode runner
|
|
43
|
+
hardening. Renovate is configured for dependency dashboard review, one-day
|
|
44
|
+
release age, grouped updates, action digest pinning, and manual approval for
|
|
45
|
+
major upgrades.
|
|
42
46
|
|
|
43
47
|
## Micro Vertical Workspaces
|
|
44
48
|
|
|
@@ -52,6 +52,7 @@ const requiredDeniedPaths = [
|
|
|
52
52
|
const requiredPostMaterialization = [
|
|
53
53
|
'ultramodern-contract-check',
|
|
54
54
|
'dependency-install-with-lifecycle-deny',
|
|
55
|
+
'github-workflow-security-enforced',
|
|
55
56
|
'package-source-retained',
|
|
56
57
|
'pnpm-11-policy-enforced',
|
|
57
58
|
'rstest-smoke-tests',
|
|
@@ -61,6 +62,7 @@ const requiredPaths = [
|
|
|
61
62
|
{{#unless isSubproject}}
|
|
62
63
|
'AGENTS.md',
|
|
63
64
|
'.agents/skills-lock.json',
|
|
65
|
+
'.github/renovate.json',
|
|
64
66
|
'.github/workflows/ultramodern-gates.yml',
|
|
65
67
|
'oxlint.config.ts',
|
|
66
68
|
'oxfmt.config.ts',
|
|
@@ -90,6 +92,53 @@ if (fs.existsSync(path.resolve(process.cwd(), 'src/routes/page.tsx'))) {
|
|
|
90
92
|
process.exit(1);
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
{{#unless isSubproject}}
|
|
96
|
+
const workflowContent = fs.readFileSync(
|
|
97
|
+
path.resolve(process.cwd(), '.github/workflows/ultramodern-gates.yml'),
|
|
98
|
+
'utf-8',
|
|
99
|
+
);
|
|
100
|
+
const renovateConfig = JSON.parse(
|
|
101
|
+
fs.readFileSync(path.resolve(process.cwd(), '.github/renovate.json'), 'utf-8'),
|
|
102
|
+
);
|
|
103
|
+
for (const requiredSnippet of [
|
|
104
|
+
'permissions:\n contents: read',
|
|
105
|
+
'pull_request:',
|
|
106
|
+
'persist-credentials: false',
|
|
107
|
+
'pnpm install --frozen-lockfile',
|
|
108
|
+
'MODERN_PUBLIC_SITE_URL: http://localhost:8080',
|
|
109
|
+
'timeout-minutes:',
|
|
110
|
+
'egress-policy: audit',
|
|
111
|
+
]) {
|
|
112
|
+
if (!workflowContent.includes(requiredSnippet)) {
|
|
113
|
+
console.error(`Generated workflow must retain ${requiredSnippet.split('\n')[0]}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (workflowContent.includes('pull_request_target')) {
|
|
118
|
+
console.error('Generated workflow must not use pull_request_target');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
for (const match of workflowContent.matchAll(/^\s*uses:\s*([^@\s]+)@([^\s#]+)/gmu)) {
|
|
122
|
+
const [, actionName, actionRef] = match;
|
|
123
|
+
if (!/^[a-f0-9]{40}$/u.test(actionRef)) {
|
|
124
|
+
console.error(`Generated workflow must pin ${actionName}@${actionRef} to a commit SHA`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (
|
|
129
|
+
renovateConfig.dependencyDashboard !== true ||
|
|
130
|
+
renovateConfig.minimumReleaseAge !== '1 day' ||
|
|
131
|
+
!renovateConfig.extends?.includes('helpers:pinGitHubActionDigests') ||
|
|
132
|
+
!renovateConfig.packageRules?.some(
|
|
133
|
+
(rule) =>
|
|
134
|
+
rule.dependencyDashboardApproval === true && rule.matchUpdateTypes?.includes('major'),
|
|
135
|
+
)
|
|
136
|
+
) {
|
|
137
|
+
console.error('Generated Renovate config must retain dashboard, release-age, action pinning, and major-approval policy');
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
{{/unless}}
|
|
141
|
+
|
|
93
142
|
const pageContent = fs.readFileSync(
|
|
94
143
|
path.resolve(process.cwd(), 'src/routes/[lang]/page.tsx'),
|
|
95
144
|
'utf-8',
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"defaultEnabled": true,
|
|
4
|
+
"installDir": "repos",
|
|
5
|
+
"repositories": [
|
|
6
|
+
{
|
|
7
|
+
"id": "effect",
|
|
8
|
+
"name": "Effect",
|
|
9
|
+
"url": "https://github.com/Effect-TS/effect.git",
|
|
10
|
+
"ref": "main",
|
|
11
|
+
"path": "repos/effect",
|
|
12
|
+
"readOnly": true
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "ultramodern-js",
|
|
16
|
+
"name": "UltraModern.js",
|
|
17
|
+
"url": "https://github.com/BleedingDev/ultramodern.js.git",
|
|
18
|
+
"ref": "main-ultramodern",
|
|
19
|
+
"path": "repos/ultramodern.js",
|
|
20
|
+
"readOnly": true
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
+
"extends": ["config:recommended", "helpers:pinGitHubActionDigests"],
|
|
4
|
+
"dependencyDashboard": true,
|
|
5
|
+
"minimumReleaseAge": "1 day",
|
|
6
|
+
"prConcurrentLimit": 5,
|
|
7
|
+
"prHourlyLimit": 2,
|
|
8
|
+
"rangeStrategy": "bump",
|
|
9
|
+
"schedule": ["before 5am on monday"],
|
|
10
|
+
"timezone": "Etc/UTC",
|
|
11
|
+
"packageRules": [
|
|
12
|
+
{
|
|
13
|
+
"matchManagers": ["github-actions"],
|
|
14
|
+
"groupName": "github-actions",
|
|
15
|
+
"labels": ["dependencies", "github-actions", "security"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"matchManagers": ["npm"],
|
|
19
|
+
"matchUpdateTypes": ["patch", "minor"],
|
|
20
|
+
"groupName": "npm minor and patch updates",
|
|
21
|
+
"labels": ["dependencies", "npm"]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"matchUpdateTypes": ["major"],
|
|
25
|
+
"dependencyDashboardApproval": true,
|
|
26
|
+
"labels": ["dependencies", "major"]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Ultramodern Workspace Gates
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
defaults:
|
|
11
|
+
run:
|
|
12
|
+
shell: bash
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ultramodern-workspace-gates-${{ github.workflow }}-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
ultramodern-workspace-gates:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 30
|
|
22
|
+
steps:
|
|
23
|
+
- name: Harden Runner
|
|
24
|
+
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2
|
|
25
|
+
with:
|
|
26
|
+
egress-policy: audit
|
|
27
|
+
|
|
28
|
+
- name: Checkout
|
|
29
|
+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 1
|
|
32
|
+
persist-credentials: false
|
|
33
|
+
|
|
34
|
+
- name: Setup pnpm
|
|
35
|
+
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
|
|
36
|
+
|
|
37
|
+
- name: Setup Node.js
|
|
38
|
+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
39
|
+
with:
|
|
40
|
+
node-version: 20
|
|
41
|
+
cache: pnpm
|
|
42
|
+
|
|
43
|
+
- name: Install Dependencies
|
|
44
|
+
run: pnpm install --frozen-lockfile
|
|
45
|
+
|
|
46
|
+
- name: Validate Ultramodern Workspace Contract
|
|
47
|
+
run: pnpm run ultramodern:check
|
|
48
|
+
|
|
49
|
+
- name: Build Workspace Apps
|
|
50
|
+
env:
|
|
51
|
+
MODERN_PUBLIC_SITE_URL: http://localhost:8080
|
|
52
|
+
run: pnpm -r --filter "./apps/**" run build
|
|
@@ -38,6 +38,15 @@ pnpm skills:install
|
|
|
38
38
|
|
|
39
39
|
The installer copies only the allowlisted private skills from `.agents/skills-lock.json`: `plan-graph`, `dag`, `subagent-graph`, `helm`, and `debugger-mode`.
|
|
40
40
|
|
|
41
|
+
## Agent Reference Repositories
|
|
42
|
+
|
|
43
|
+
The workspace installs read-only source snapshots under `repos/` by default during `pnpm install`. These repositories are reference material for coding agents, not application source:
|
|
44
|
+
|
|
45
|
+
- `repos/effect` from `Effect-TS/effect`.
|
|
46
|
+
- `repos/ultramodern.js` from `BleedingDev/ultramodern.js`.
|
|
47
|
+
|
|
48
|
+
Agents may read files under `repos/` to understand upstream patterns, APIs, and project conventions. Do not edit files under `repos/`, import from them, or make production code depend on them. To skip this setup, run installs with `ULTRAMODERN_SKIP_AGENT_REPOS=1`.
|
|
49
|
+
|
|
41
50
|
## Project Priorities
|
|
42
51
|
|
|
43
52
|
- Keep `presetUltramodern` as the single preset.
|
|
@@ -19,7 +19,17 @@ Run the scaffold validator before adding business code:
|
|
|
19
19
|
pnpm ultramodern:check
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
By default, `pnpm install` also prepares read-only agent reference repositories
|
|
23
|
+
under `repos/` for Effect and UltraModern.js source lookup. Disable this setup
|
|
24
|
+
with `ULTRAMODERN_SKIP_AGENT_REPOS=1 pnpm install`, or rerun it explicitly with
|
|
25
|
+
`pnpm agents:refs:install`.
|
|
26
|
+
|
|
27
|
+
The topology and ownership metadata are generated under `topology/`. The
|
|
28
|
+
workspace also ships `.github/workflows/ultramodern-workspace-gates.yml` and
|
|
29
|
+
`.github/renovate.json` with read-only workflow permissions, commit-pinned
|
|
30
|
+
actions, frozen installs, StepSecurity audit-mode runner hardening, dependency
|
|
31
|
+
dashboard review, one-day release age, grouped updates, and manual approval for
|
|
32
|
+
major upgrades.
|
|
23
33
|
|
|
24
34
|
Package source metadata is generated at
|
|
25
35
|
`.modernjs/ultramodern-package-source.json`. The default strategy keeps
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const root = process.cwd();
|
|
6
|
+
const args = new Set(process.argv.slice(2));
|
|
7
|
+
const checkOnly = args.has('--check');
|
|
8
|
+
const configPath = path.join(root, '.agents', 'agent-reference-repos.json');
|
|
9
|
+
const manifestPath = path.join(root, '.modernjs', 'agent-reference-repos.json');
|
|
10
|
+
const tempRoot = path.join(root, '.modernjs', 'agent-reference-repos-tmp');
|
|
11
|
+
|
|
12
|
+
const truthy = value => /^(1|true|yes|on)$/i.test(String(value ?? ''));
|
|
13
|
+
const falsy = value => /^(0|false|no|off)$/i.test(String(value ?? ''));
|
|
14
|
+
|
|
15
|
+
const skipRequested =
|
|
16
|
+
truthy(process.env.ULTRAMODERN_SKIP_AGENT_REPOS) ||
|
|
17
|
+
falsy(process.env.ULTRAMODERN_AGENT_REPOS);
|
|
18
|
+
const required = truthy(process.env.ULTRAMODERN_AGENT_REPOS_REQUIRED);
|
|
19
|
+
const refresh = truthy(process.env.ULTRAMODERN_AGENT_REPOS_REFRESH);
|
|
20
|
+
|
|
21
|
+
const log = message => console.log(`[agent-reference-repos] ${message}`);
|
|
22
|
+
const warn = message => console.warn(`[agent-reference-repos] ${message}`);
|
|
23
|
+
|
|
24
|
+
function fail(message) {
|
|
25
|
+
if (required || checkOnly) {
|
|
26
|
+
throw new Error(message);
|
|
27
|
+
}
|
|
28
|
+
warn(message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readJson(filePath) {
|
|
32
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function run(command, commandArgs, options = {}) {
|
|
36
|
+
const result = spawnSync(command, commandArgs, {
|
|
37
|
+
cwd: options.cwd ?? root,
|
|
38
|
+
encoding: 'utf-8',
|
|
39
|
+
stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
|
|
40
|
+
timeout: options.timeout ?? 120000,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (result.error) {
|
|
44
|
+
throw result.error;
|
|
45
|
+
}
|
|
46
|
+
if (result.status !== 0) {
|
|
47
|
+
const stderr = result.stderr?.trim();
|
|
48
|
+
throw new Error(
|
|
49
|
+
`${command} ${commandArgs.join(' ')} failed${
|
|
50
|
+
stderr ? `: ${stderr}` : ''
|
|
51
|
+
}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return result.stdout?.trim() ?? '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertSafeRepoPath(relativePath) {
|
|
58
|
+
if (
|
|
59
|
+
typeof relativePath !== 'string' ||
|
|
60
|
+
relativePath.length === 0 ||
|
|
61
|
+
path.isAbsolute(relativePath) ||
|
|
62
|
+
relativePath.split(/[\\/]+/).includes('..') ||
|
|
63
|
+
!relativePath.startsWith('repos/')
|
|
64
|
+
) {
|
|
65
|
+
throw new Error(`Unsafe reference repository path: ${relativePath}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hasGit() {
|
|
70
|
+
const result = spawnSync('git', ['--version'], {
|
|
71
|
+
encoding: 'utf-8',
|
|
72
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
73
|
+
});
|
|
74
|
+
return result.status === 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function existingReference(targetPath, repo) {
|
|
78
|
+
const markerPath = path.join(targetPath, '.ultramodern-reference-repo.json');
|
|
79
|
+
if (!fs.existsSync(markerPath)) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const marker = readJson(markerPath);
|
|
84
|
+
if (marker.url === repo.url && marker.ref === repo.ref) {
|
|
85
|
+
return marker;
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function materializeRepository(repo) {
|
|
94
|
+
assertSafeRepoPath(repo.path);
|
|
95
|
+
const targetPath = path.join(root, repo.path);
|
|
96
|
+
const existing = existingReference(targetPath, repo);
|
|
97
|
+
|
|
98
|
+
if (existing && !refresh) {
|
|
99
|
+
log(`${repo.id} already present at ${repo.path} (${existing.commit})`);
|
|
100
|
+
return { ...existing, status: 'present' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (fs.existsSync(targetPath)) {
|
|
104
|
+
if (!refresh) {
|
|
105
|
+
fail(`${repo.path} exists but is not a managed reference repo`);
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (checkOnly) {
|
|
112
|
+
fail(`${repo.path} is missing`);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fs.mkdirSync(tempRoot, { recursive: true });
|
|
117
|
+
const tempPath = fs.mkdtempSync(path.join(tempRoot, `${repo.id}-`));
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
log(`cloning ${repo.name} from ${repo.url}#${repo.ref}`);
|
|
121
|
+
run(
|
|
122
|
+
'git',
|
|
123
|
+
[
|
|
124
|
+
'clone',
|
|
125
|
+
'--depth',
|
|
126
|
+
'1',
|
|
127
|
+
'--single-branch',
|
|
128
|
+
'--branch',
|
|
129
|
+
repo.ref,
|
|
130
|
+
'--filter=blob:none',
|
|
131
|
+
repo.url,
|
|
132
|
+
tempPath,
|
|
133
|
+
],
|
|
134
|
+
{ timeout: 300000 },
|
|
135
|
+
);
|
|
136
|
+
const commit = run('git', ['-C', tempPath, 'rev-parse', 'HEAD']);
|
|
137
|
+
fs.rmSync(path.join(tempPath, '.git'), { recursive: true, force: true });
|
|
138
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
139
|
+
fs.renameSync(tempPath, targetPath);
|
|
140
|
+
|
|
141
|
+
const marker = {
|
|
142
|
+
schemaVersion: 1,
|
|
143
|
+
id: repo.id,
|
|
144
|
+
name: repo.name,
|
|
145
|
+
url: repo.url,
|
|
146
|
+
ref: repo.ref,
|
|
147
|
+
commit,
|
|
148
|
+
path: repo.path,
|
|
149
|
+
readOnly: repo.readOnly !== false,
|
|
150
|
+
installedAt: new Date().toISOString(),
|
|
151
|
+
};
|
|
152
|
+
fs.writeFileSync(
|
|
153
|
+
path.join(targetPath, '.ultramodern-reference-repo.json'),
|
|
154
|
+
`${JSON.stringify(marker, null, 2)}\n`,
|
|
155
|
+
);
|
|
156
|
+
log(`${repo.id} installed at ${repo.path} (${commit})`);
|
|
157
|
+
return { ...marker, status: 'installed' };
|
|
158
|
+
} catch (error) {
|
|
159
|
+
fs.rmSync(tempPath, { recursive: true, force: true });
|
|
160
|
+
fail(`Could not install ${repo.id}: ${error.message}`);
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function main() {
|
|
166
|
+
if (!fs.existsSync(configPath)) {
|
|
167
|
+
fail('Missing .agents/agent-reference-repos.json');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const config = readJson(configPath);
|
|
172
|
+
const enabled = config.defaultEnabled !== false && !skipRequested;
|
|
173
|
+
|
|
174
|
+
if (!enabled) {
|
|
175
|
+
log('setup skipped; set ULTRAMODERN_SKIP_AGENT_REPOS=0 to enable it again');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!hasGit()) {
|
|
180
|
+
fail('git is required to install agent reference repositories');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const installed = [];
|
|
185
|
+
for (const repo of config.repositories ?? []) {
|
|
186
|
+
const result = materializeRepository(repo);
|
|
187
|
+
if (result) {
|
|
188
|
+
installed.push(result);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!checkOnly) {
|
|
193
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
194
|
+
fs.writeFileSync(
|
|
195
|
+
manifestPath,
|
|
196
|
+
`${JSON.stringify(
|
|
197
|
+
{
|
|
198
|
+
schemaVersion: 1,
|
|
199
|
+
generatedAt: new Date().toISOString(),
|
|
200
|
+
installDir: config.installDir ?? 'repos',
|
|
201
|
+
repositories: installed,
|
|
202
|
+
},
|
|
203
|
+
null,
|
|
204
|
+
2,
|
|
205
|
+
)}\n`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
main();
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(`[agent-reference-repos] ${error.message}`);
|
|
214
|
+
process.exitCode = 1;
|
|
215
|
+
} finally {
|
|
216
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
217
|
+
}
|
|
@@ -36,12 +36,16 @@ const assertExists = (relativePath) => {
|
|
|
36
36
|
|
|
37
37
|
const requiredPaths = [
|
|
38
38
|
'AGENTS.md',
|
|
39
|
+
'.gitignore',
|
|
39
40
|
'package.json',
|
|
40
41
|
'pnpm-workspace.yaml',
|
|
41
42
|
'tsconfig.base.json',
|
|
42
43
|
'oxlint.config.ts',
|
|
43
44
|
'oxfmt.config.ts',
|
|
45
|
+
'.github/renovate.json',
|
|
46
|
+
'.github/workflows/ultramodern-workspace-gates.yml',
|
|
44
47
|
'.agents/skills-lock.json',
|
|
48
|
+
'.agents/agent-reference-repos.json',
|
|
45
49
|
'.agents/rstackjs-agent-skills-LICENSE',
|
|
46
50
|
'.agents/skills/rsbuild-best-practices/SKILL.md',
|
|
47
51
|
'.agents/skills/rspack-best-practices/SKILL.md',
|
|
@@ -59,6 +63,7 @@ const requiredPaths = [
|
|
|
59
63
|
'.modernjs/ultramodern-workspace-template-manifest.json',
|
|
60
64
|
'.modernjs/ultramodern-package-source.json',
|
|
61
65
|
'scripts/bootstrap-agent-skills.mjs',
|
|
66
|
+
'scripts/setup-agent-reference-repos.mjs',
|
|
62
67
|
'scripts/check-i18n-strings.mjs',
|
|
63
68
|
'apps/shell-super-app/package.json',
|
|
64
69
|
'apps/shell-super-app/config/public/locales/en/translation.json',
|
|
@@ -120,7 +125,10 @@ for (const appDirectory of [
|
|
|
120
125
|
const rootPackage = readJson('package.json');
|
|
121
126
|
const packageSource = readJson('.modernjs/ultramodern-package-source.json');
|
|
122
127
|
const skillsLock = readJson('.agents/skills-lock.json');
|
|
128
|
+
const agentReferenceRepos = readJson('.agents/agent-reference-repos.json');
|
|
123
129
|
const pnpmWorkspace = readText('pnpm-workspace.yaml');
|
|
130
|
+
const workflowContent = readText('.github/workflows/ultramodern-workspace-gates.yml');
|
|
131
|
+
const renovateConfig = readJson('.github/renovate.json');
|
|
124
132
|
const deprecatedTanstackRuntime = '@modern-js/runtime/' + 'tanstack-router';
|
|
125
133
|
const expectedModernSpecifier =
|
|
126
134
|
packageSource.strategy === 'install' ? packageSource.modernPackages?.specifier : 'workspace:*';
|
|
@@ -164,6 +172,42 @@ assert(
|
|
|
164
172
|
!pnpmWorkspace.includes('onlyBuiltDependencies'),
|
|
165
173
|
'pnpm-workspace.yaml must use pnpm 11 allowBuilds instead of onlyBuiltDependencies',
|
|
166
174
|
);
|
|
175
|
+
for (const requiredSnippet of [
|
|
176
|
+
'permissions:\n contents: read',
|
|
177
|
+
'pull_request:',
|
|
178
|
+
'persist-credentials: false',
|
|
179
|
+
'pnpm install --frozen-lockfile',
|
|
180
|
+
'pnpm -r --filter "./apps/**" run build',
|
|
181
|
+
'MODERN_PUBLIC_SITE_URL: http://localhost:8080',
|
|
182
|
+
'timeout-minutes:',
|
|
183
|
+
'egress-policy: audit',
|
|
184
|
+
]) {
|
|
185
|
+
assert(
|
|
186
|
+
workflowContent.includes(requiredSnippet),
|
|
187
|
+
`Generated workspace workflow must retain ${requiredSnippet.split('\n')[0]}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
assert(
|
|
191
|
+
!workflowContent.includes('pull_request_target'),
|
|
192
|
+
'Generated workspace workflow must not use pull_request_target',
|
|
193
|
+
);
|
|
194
|
+
for (const match of workflowContent.matchAll(/^\s*uses:\s*([^@\s]+)@([^\s#]+)/gmu)) {
|
|
195
|
+
const [, actionName, actionRef] = match;
|
|
196
|
+
assert(
|
|
197
|
+
/^[a-f0-9]{40}$/u.test(actionRef),
|
|
198
|
+
`Generated workspace workflow must pin ${actionName}@${actionRef} to a commit SHA`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
assert(
|
|
202
|
+
renovateConfig.dependencyDashboard === true &&
|
|
203
|
+
renovateConfig.minimumReleaseAge === '1 day' &&
|
|
204
|
+
renovateConfig.extends?.includes('helpers:pinGitHubActionDigests') &&
|
|
205
|
+
renovateConfig.packageRules?.some(
|
|
206
|
+
(rule) =>
|
|
207
|
+
rule.dependencyDashboardApproval === true && rule.matchUpdateTypes?.includes('major'),
|
|
208
|
+
),
|
|
209
|
+
'Generated workspace Renovate config must retain dashboard, release-age, action pinning, and major-approval policy',
|
|
210
|
+
);
|
|
167
211
|
assert(
|
|
168
212
|
rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json',
|
|
169
213
|
'Root must point to the UltraModern package source metadata',
|
|
@@ -197,6 +241,9 @@ const requiredRootScripts = {
|
|
|
197
241
|
'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
|
|
198
242
|
lint: 'oxlint .',
|
|
199
243
|
'lint:fix': 'oxlint . --fix',
|
|
244
|
+
'agents:refs:check': 'node ./scripts/setup-agent-reference-repos.mjs --check',
|
|
245
|
+
'agents:refs:install': 'node ./scripts/setup-agent-reference-repos.mjs',
|
|
246
|
+
postinstall: 'node ./scripts/setup-agent-reference-repos.mjs',
|
|
200
247
|
'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
|
|
201
248
|
'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
|
|
202
249
|
typecheck: `pnpm -r --filter "@${packageScope}/*" typecheck`,
|
|
@@ -234,6 +281,34 @@ assert(
|
|
|
234
281
|
skillsLock.installDir === '.agents/skills',
|
|
235
282
|
'Agent skills lock must use .agents/skills as installDir',
|
|
236
283
|
);
|
|
284
|
+
assert(
|
|
285
|
+
agentReferenceRepos.defaultEnabled === true,
|
|
286
|
+
'Agent reference repositories must be enabled by default',
|
|
287
|
+
);
|
|
288
|
+
assert(
|
|
289
|
+
agentReferenceRepos.repositories?.some(
|
|
290
|
+
(repo) =>
|
|
291
|
+
repo.id === 'effect' &&
|
|
292
|
+
repo.url === 'https://github.com/Effect-TS/effect.git' &&
|
|
293
|
+
repo.path === 'repos/effect',
|
|
294
|
+
),
|
|
295
|
+
'Agent reference repositories must include Effect',
|
|
296
|
+
);
|
|
297
|
+
assert(
|
|
298
|
+
agentReferenceRepos.repositories?.some(
|
|
299
|
+
(repo) =>
|
|
300
|
+
repo.id === 'ultramodern-js' &&
|
|
301
|
+
repo.url === 'https://github.com/BleedingDev/ultramodern.js.git' &&
|
|
302
|
+
repo.path === 'repos/ultramodern.js',
|
|
303
|
+
),
|
|
304
|
+
'Agent reference repositories must include UltraModern.js',
|
|
305
|
+
);
|
|
306
|
+
assert(
|
|
307
|
+
readText('scripts/setup-agent-reference-repos.mjs').includes(
|
|
308
|
+
'ULTRAMODERN_SKIP_AGENT_REPOS',
|
|
309
|
+
),
|
|
310
|
+
'Agent reference repository setup must expose an opt-out environment variable',
|
|
311
|
+
);
|
|
237
312
|
for (const skillName of baselineAgentSkills) {
|
|
238
313
|
assert(
|
|
239
314
|
skillsLock.baseline?.some((skill) => skill.name === skillName),
|
|
@@ -317,6 +392,10 @@ for (const packagePath of appPackagePaths) {
|
|
|
317
392
|
packageJson.dependencies?.['@module-federation/modern-js-v3'] === '2.4.0',
|
|
318
393
|
`${packagePath} must include the Module Federation plugin`,
|
|
319
394
|
);
|
|
395
|
+
assert(
|
|
396
|
+
packageJson.dependencies?.['zephyr-modernjs-plugin'] === '1.1.1',
|
|
397
|
+
`${packagePath} must include the official Zephyr Modern.js plugin`,
|
|
398
|
+
);
|
|
320
399
|
assert(
|
|
321
400
|
packageJson.modernjs?.preset === 'presetUltramodern',
|
|
322
401
|
`${packagePath} must keep presetUltramodern metadata`,
|
|
@@ -343,6 +422,27 @@ for (const configPath of [
|
|
|
343
422
|
config.includes('moduleFederationPlugin()'),
|
|
344
423
|
`${configPath} must enable Module Federation`,
|
|
345
424
|
);
|
|
425
|
+
assert(
|
|
426
|
+
config.includes("from 'zephyr-modernjs-plugin'"),
|
|
427
|
+
`${configPath} must import the official Zephyr Modern.js plugin`,
|
|
428
|
+
);
|
|
429
|
+
assert(config.includes('withZephyr()'), `${configPath} must enable Zephyr through plugins`);
|
|
430
|
+
assert(
|
|
431
|
+
config.includes("bundler: 'rspack'"),
|
|
432
|
+
`${configPath} must keep appTools on the rspack bundler for Zephyr`,
|
|
433
|
+
);
|
|
434
|
+
assert(
|
|
435
|
+
config.includes("html: './'"),
|
|
436
|
+
`${configPath} must keep Modern.js output.distPath.html compatible with Zephyr`,
|
|
437
|
+
);
|
|
438
|
+
assert(
|
|
439
|
+
config.includes("outputStructure: 'flat'"),
|
|
440
|
+
`${configPath} must keep Modern.js HTML output flat for Zephyr`,
|
|
441
|
+
);
|
|
442
|
+
assert(
|
|
443
|
+
config.includes("mainEntryName: 'index'"),
|
|
444
|
+
`${configPath} must keep Modern.js source.mainEntryName compatible with Zephyr`,
|
|
445
|
+
);
|
|
346
446
|
}
|
|
347
447
|
|
|
348
448
|
for (const routePath of [
|
|
@@ -506,5 +606,9 @@ assert(
|
|
|
506
606
|
manifest.validation?.postMaterializationValidation?.includes('pnpm-11-policy-enforced'),
|
|
507
607
|
'Template manifest must document pnpm 11 policy validation',
|
|
508
608
|
);
|
|
609
|
+
assert(
|
|
610
|
+
manifest.validation?.postMaterializationValidation?.includes('github-workflow-security-enforced'),
|
|
611
|
+
'Template manifest must document generated workflow security validation',
|
|
612
|
+
);
|
|
509
613
|
|
|
510
614
|
console.log('UltraModern workspace scaffold validated');
|