@bleedingdev/modern-js-create 3.2.0-ultramodern.9 → 3.2.0-ultramodern.90
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 +152 -35
- package/dist/index.js +4700 -608
- 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/.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 +9 -6
- package/template/README.md +60 -34
- package/template/api/effect/index.ts.handlebars +8 -3
- package/template/config/public/locales/cs/translation.json +39 -0
- package/template/config/public/locales/en/translation.json +39 -0
- package/template/lefthook.yml +10 -0
- package/template/modern.config.ts.handlebars +39 -24
- package/template/oxfmt.config.ts +11 -3
- package/template/oxlint.config.ts +11 -4
- package/template/package.json.handlebars +43 -34
- package/template/pnpm-workspace.yaml +29 -0
- package/template/rstest.config.mts +5 -0
- package/template/scripts/bootstrap-agent-skills.mjs +160 -35
- package/template/scripts/check-i18n-strings.mjs +94 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +387 -35
- 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 +211 -0
- package/template/src/routes/index.css.handlebars +14 -3
- package/template/src/routes/layout.tsx.handlebars +2 -1
- package/template/tailwind.config.ts.handlebars +1 -1
- package/template/tests/tsconfig.json +7 -0
- package/template/tests/ultramodern.contract.test.ts.handlebars +78 -0
- package/template-workspace/.agents/agent-reference-repos.json +24 -0
- package/template-workspace/.agents/skills-lock.json +19 -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 +36 -5
- package/template-workspace/README.md.handlebars +61 -11
- package/template-workspace/lefthook.yml +10 -0
- package/template-workspace/oxfmt.config.ts +13 -3
- package/template-workspace/oxlint.config.ts +12 -4
- package/template-workspace/pnpm-workspace.yaml +26 -8
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +184 -26
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +368 -0
- package/template/src/routes/page.tsx.handlebars +0 -119
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -403
|
@@ -0,0 +1,368 @@
|
|
|
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
|
+
|
|
11
|
+
const truthy = value => /^(1|true|yes|on)$/i.test(String(value ?? ''));
|
|
12
|
+
const falsy = value => /^(0|false|no|off)$/i.test(String(value ?? ''));
|
|
13
|
+
|
|
14
|
+
const skipRequested =
|
|
15
|
+
truthy(process.env.ULTRAMODERN_SKIP_AGENT_REPOS) ||
|
|
16
|
+
falsy(process.env.ULTRAMODERN_AGENT_REPOS);
|
|
17
|
+
const required = truthy(process.env.ULTRAMODERN_AGENT_REPOS_REQUIRED);
|
|
18
|
+
const refresh = truthy(process.env.ULTRAMODERN_AGENT_REPOS_REFRESH);
|
|
19
|
+
|
|
20
|
+
const gitIdentityEnv = {
|
|
21
|
+
GIT_AUTHOR_NAME:
|
|
22
|
+
process.env.GIT_AUTHOR_NAME || 'UltraModern Agent Reference Setup',
|
|
23
|
+
GIT_AUTHOR_EMAIL:
|
|
24
|
+
process.env.GIT_AUTHOR_EMAIL || 'ultramodern-agent-refs@local',
|
|
25
|
+
GIT_COMMITTER_NAME:
|
|
26
|
+
process.env.GIT_COMMITTER_NAME || 'UltraModern Agent Reference Setup',
|
|
27
|
+
GIT_COMMITTER_EMAIL:
|
|
28
|
+
process.env.GIT_COMMITTER_EMAIL || 'ultramodern-agent-refs@local',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const log = message => console.log(`[agent-reference-repos] ${message}`);
|
|
32
|
+
const warn = message => console.warn(`[agent-reference-repos] ${message}`);
|
|
33
|
+
|
|
34
|
+
function fail(message) {
|
|
35
|
+
if (required || checkOnly) {
|
|
36
|
+
throw new Error(message);
|
|
37
|
+
}
|
|
38
|
+
warn(message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readJson(filePath) {
|
|
42
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function run(command, commandArgs, options = {}) {
|
|
46
|
+
const result = spawnSync(command, commandArgs, {
|
|
47
|
+
cwd: options.cwd ?? root,
|
|
48
|
+
encoding: 'utf-8',
|
|
49
|
+
env: {
|
|
50
|
+
...process.env,
|
|
51
|
+
...gitIdentityEnv,
|
|
52
|
+
...(options.env ?? {}),
|
|
53
|
+
},
|
|
54
|
+
stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
|
|
55
|
+
timeout: options.timeout ?? 120000,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (result.error) {
|
|
59
|
+
throw result.error;
|
|
60
|
+
}
|
|
61
|
+
if (result.status !== 0) {
|
|
62
|
+
const stderr = result.stderr?.trim();
|
|
63
|
+
throw new Error(
|
|
64
|
+
`${command} ${commandArgs.join(' ')} failed${
|
|
65
|
+
stderr ? `: ${stderr}` : ''
|
|
66
|
+
}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return result.stdout?.trim() ?? '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function assertSafeRepoPath(relativePath) {
|
|
73
|
+
if (
|
|
74
|
+
typeof relativePath !== 'string' ||
|
|
75
|
+
relativePath.length === 0 ||
|
|
76
|
+
path.isAbsolute(relativePath) ||
|
|
77
|
+
relativePath.split(/[\\/]+/).includes('..') ||
|
|
78
|
+
!relativePath.startsWith('repos/')
|
|
79
|
+
) {
|
|
80
|
+
throw new Error(`Unsafe reference repository path: ${relativePath}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hasGit() {
|
|
85
|
+
const result = spawnSync('git', ['--version'], {
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
88
|
+
});
|
|
89
|
+
return result.status === 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function hasGitSubtree() {
|
|
93
|
+
const result = spawnSync('git', ['subtree', '-h'], {
|
|
94
|
+
encoding: 'utf-8',
|
|
95
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
96
|
+
});
|
|
97
|
+
return (
|
|
98
|
+
(result.status === 0 || result.status === 129) &&
|
|
99
|
+
result.stdout.includes('usage: git subtree')
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isGitWorkTree() {
|
|
104
|
+
const result = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
105
|
+
cwd: root,
|
|
106
|
+
encoding: 'utf-8',
|
|
107
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
108
|
+
});
|
|
109
|
+
return result.status === 0 && result.stdout.trim() === 'true';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function hasCommits() {
|
|
113
|
+
const result = spawnSync('git', ['rev-parse', '--verify', 'HEAD'], {
|
|
114
|
+
cwd: root,
|
|
115
|
+
encoding: 'utf-8',
|
|
116
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
117
|
+
});
|
|
118
|
+
return result.status === 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function porcelainStatus() {
|
|
122
|
+
return run('git', ['status', '--porcelain'], { timeout: 30000 });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ensureGitRepository() {
|
|
126
|
+
if (!isGitWorkTree()) {
|
|
127
|
+
if (checkOnly) {
|
|
128
|
+
fail('workspace is not a git repository');
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
log('initializing git repository for agent reference subtrees');
|
|
132
|
+
run('git', ['init'], { timeout: 30000 });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!hasCommits()) {
|
|
136
|
+
if (checkOnly) {
|
|
137
|
+
fail('workspace has no initial git commit');
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
log('creating initial workspace commit before adding reference subtrees');
|
|
141
|
+
run('git', ['add', '-A'], { timeout: 30000 });
|
|
142
|
+
run('git', ['commit', '-m', 'Initialize UltraModern workspace'], {
|
|
143
|
+
timeout: 120000,
|
|
144
|
+
});
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const status = porcelainStatus();
|
|
149
|
+
if (status) {
|
|
150
|
+
fail(
|
|
151
|
+
'workspace has uncommitted changes; commit or stash them before installing reference subtrees',
|
|
152
|
+
);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function remoteCommit(repo) {
|
|
160
|
+
let output = run('git', ['ls-remote', repo.url, `refs/heads/${repo.ref}`], {
|
|
161
|
+
timeout: 120000,
|
|
162
|
+
});
|
|
163
|
+
if (!output) {
|
|
164
|
+
output = run('git', ['ls-remote', repo.url, repo.ref], {
|
|
165
|
+
timeout: 120000,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const [commit] = output.split(/\s+/);
|
|
169
|
+
if (!/^[a-f0-9]{40}$/i.test(commit ?? '')) {
|
|
170
|
+
throw new Error(`Could not resolve ${repo.url}#${repo.ref}`);
|
|
171
|
+
}
|
|
172
|
+
return commit;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function subtreeCommitExists(repo) {
|
|
176
|
+
const result = spawnSync(
|
|
177
|
+
'git',
|
|
178
|
+
[
|
|
179
|
+
'log',
|
|
180
|
+
'--grep',
|
|
181
|
+
`git-subtree-dir: ${repo.path}`,
|
|
182
|
+
'--format=%H',
|
|
183
|
+
'-n',
|
|
184
|
+
'1',
|
|
185
|
+
],
|
|
186
|
+
{
|
|
187
|
+
cwd: root,
|
|
188
|
+
encoding: 'utf-8',
|
|
189
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
return result.status === 0 && result.stdout.trim().length > 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function installedManifestEntry(repo) {
|
|
196
|
+
if (!fs.existsSync(manifestPath)) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const manifest = readJson(manifestPath);
|
|
201
|
+
return manifest.repositories?.find(entry => entry.id === repo.id);
|
|
202
|
+
} catch {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function assertSubtreePresent(repo) {
|
|
208
|
+
assertSafeRepoPath(repo.path);
|
|
209
|
+
const targetPath = path.join(root, repo.path);
|
|
210
|
+
if (!fs.existsSync(targetPath)) {
|
|
211
|
+
fail(`${repo.path} is missing`);
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
if (!subtreeCommitExists(repo)) {
|
|
215
|
+
fail(`${repo.path} is present but has no git-subtree commit evidence`);
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
return (
|
|
219
|
+
installedManifestEntry(repo) ?? {
|
|
220
|
+
id: repo.id,
|
|
221
|
+
name: repo.name,
|
|
222
|
+
url: repo.url,
|
|
223
|
+
ref: repo.ref,
|
|
224
|
+
path: repo.path,
|
|
225
|
+
readOnly: repo.readOnly !== false,
|
|
226
|
+
status: 'present',
|
|
227
|
+
strategy: 'git-subtree-squash',
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function addSubtree(repo) {
|
|
233
|
+
assertSafeRepoPath(repo.path);
|
|
234
|
+
const targetPath = path.join(root, repo.path);
|
|
235
|
+
const existing = fs.existsSync(targetPath);
|
|
236
|
+
|
|
237
|
+
if (existing && !refresh) {
|
|
238
|
+
return assertSubtreePresent(repo);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (existing && refresh) {
|
|
242
|
+
fail(
|
|
243
|
+
`${repo.path} already exists; refresh for subtree references is intentionally manual`,
|
|
244
|
+
);
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (checkOnly) {
|
|
249
|
+
fail(`${repo.path} is missing`);
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const commit = remoteCommit(repo);
|
|
254
|
+
log(`adding ${repo.name} as git subtree at ${repo.path} (${commit})`);
|
|
255
|
+
run('git', ['fetch', '--depth', '1', repo.url, repo.ref], {
|
|
256
|
+
timeout: 300000,
|
|
257
|
+
});
|
|
258
|
+
run(
|
|
259
|
+
'git',
|
|
260
|
+
[
|
|
261
|
+
'subtree',
|
|
262
|
+
'add',
|
|
263
|
+
'--prefix',
|
|
264
|
+
repo.path,
|
|
265
|
+
'FETCH_HEAD',
|
|
266
|
+
'--squash',
|
|
267
|
+
'-m',
|
|
268
|
+
`Add ${repo.name} agent reference repo`,
|
|
269
|
+
],
|
|
270
|
+
{ timeout: 600000 },
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
schemaVersion: 1,
|
|
275
|
+
id: repo.id,
|
|
276
|
+
name: repo.name,
|
|
277
|
+
url: repo.url,
|
|
278
|
+
ref: repo.ref,
|
|
279
|
+
commit,
|
|
280
|
+
path: repo.path,
|
|
281
|
+
readOnly: repo.readOnly !== false,
|
|
282
|
+
strategy: 'git-subtree-squash',
|
|
283
|
+
status: 'installed',
|
|
284
|
+
installedAt: new Date().toISOString(),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function writeManifest(entries) {
|
|
289
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
290
|
+
fs.writeFileSync(
|
|
291
|
+
manifestPath,
|
|
292
|
+
`${JSON.stringify(
|
|
293
|
+
{
|
|
294
|
+
schemaVersion: 1,
|
|
295
|
+
generatedAt: new Date().toISOString(),
|
|
296
|
+
strategy: 'git-subtree-squash',
|
|
297
|
+
installDir: 'repos',
|
|
298
|
+
repositories: entries,
|
|
299
|
+
},
|
|
300
|
+
null,
|
|
301
|
+
2,
|
|
302
|
+
)}\n`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function commitManifestIfChanged() {
|
|
307
|
+
const status = run('git', ['status', '--porcelain', '--', manifestPath], {
|
|
308
|
+
timeout: 30000,
|
|
309
|
+
});
|
|
310
|
+
if (!status) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
run('git', ['add', manifestPath], { timeout: 30000 });
|
|
314
|
+
run('git', ['commit', '-m', 'Record agent reference repo manifest'], {
|
|
315
|
+
timeout: 120000,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function main() {
|
|
320
|
+
if (!fs.existsSync(configPath)) {
|
|
321
|
+
fail('Missing .agents/agent-reference-repos.json');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const config = readJson(configPath);
|
|
326
|
+
const enabled = config.defaultEnabled !== false && !skipRequested;
|
|
327
|
+
|
|
328
|
+
if (!enabled) {
|
|
329
|
+
log('setup skipped; set ULTRAMODERN_SKIP_AGENT_REPOS=0 to enable it again');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!hasGit()) {
|
|
334
|
+
fail('git is required to install agent reference repositories');
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (!hasGitSubtree()) {
|
|
338
|
+
fail('git subtree is required to install agent reference repositories');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (!ensureGitRepository()) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const entries = [];
|
|
346
|
+
for (const repo of config.repositories ?? []) {
|
|
347
|
+
const result = checkOnly ? assertSubtreePresent(repo) : addSubtree(repo);
|
|
348
|
+
if (result) {
|
|
349
|
+
entries.push(result);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (!checkOnly) {
|
|
354
|
+
writeManifest(entries);
|
|
355
|
+
commitManifestIfChanged();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
main();
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (required || checkOnly) {
|
|
363
|
+
console.error(`[agent-reference-repos] ${error.message}`);
|
|
364
|
+
process.exitCode = 1;
|
|
365
|
+
} else {
|
|
366
|
+
warn(error.message);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { Helmet } from '@modern-js/runtime/head';
|
|
2
|
-
{{#if useEffectBff}}import effectBff from '@api/effect/index';
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
|
-
{{/if}}
|
|
5
|
-
import './index.css';
|
|
6
|
-
|
|
7
|
-
const Index = () => {
|
|
8
|
-
{{#if useEffectBff}}
|
|
9
|
-
const [effectMessage, setEffectMessage] = useState('loading...');
|
|
10
|
-
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
effectBff.client.greetings.hello({}).then(data => {
|
|
13
|
-
setEffectMessage(data.message);
|
|
14
|
-
});
|
|
15
|
-
}, []);
|
|
16
|
-
{{/if}}
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<div className="container-box">
|
|
20
|
-
<Helmet>
|
|
21
|
-
<link
|
|
22
|
-
rel="icon"
|
|
23
|
-
type="image/x-icon"
|
|
24
|
-
href="https://lf3-static.bytednsdoc.com/obj/eden-cn/uhbfnupenuhf/favicon.ico"
|
|
25
|
-
/>
|
|
26
|
-
</Helmet>
|
|
27
|
-
<main>
|
|
28
|
-
<div className="title">
|
|
29
|
-
UltraModern.js 3.0
|
|
30
|
-
<img
|
|
31
|
-
className="logo"
|
|
32
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/modern-js-logo.svg"
|
|
33
|
-
alt="UltraModern.js Logo"
|
|
34
|
-
/>
|
|
35
|
-
<p className="name">presetUltramodern</p>
|
|
36
|
-
</div>
|
|
37
|
-
<p className="description{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
|
|
38
|
-
This starter ships the public <code className="code">presetUltramodern(...)</code> profile. Start in
|
|
39
|
-
<code className="code">modern.config.ts</code>, keep
|
|
40
|
-
<code className="code">pnpm run ultramodern:check</code> green, and
|
|
41
|
-
tune the generated preset only where your app needs a softer lane.
|
|
42
|
-
</p>
|
|
43
|
-
{{#if useEffectBff}}
|
|
44
|
-
<p className="description effect-message{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
|
|
45
|
-
Effect HttpApi response: <code className="code">{effectMessage}</code>
|
|
46
|
-
</p>
|
|
47
|
-
{{/if}}
|
|
48
|
-
<div className="grid">
|
|
49
|
-
<a
|
|
50
|
-
href="https://bleedingdev.github.io/ultramodern.js/guides/get-started/ultramodern.html"
|
|
51
|
-
target="_blank"
|
|
52
|
-
rel="noopener noreferrer"
|
|
53
|
-
className="card"
|
|
54
|
-
>
|
|
55
|
-
<h2>
|
|
56
|
-
UltraModern.js Guide
|
|
57
|
-
<img
|
|
58
|
-
className="arrow-right"
|
|
59
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
|
|
60
|
-
alt="Guide"
|
|
61
|
-
/>
|
|
62
|
-
</h2>
|
|
63
|
-
<p>Review the MV-first, TanStack-ready, Effect-ready public preset.</p>
|
|
64
|
-
</a>
|
|
65
|
-
<a
|
|
66
|
-
href="https://bleedingdev.github.io/ultramodern.js/configure/app/usage.html"
|
|
67
|
-
target="_blank"
|
|
68
|
-
className="card"
|
|
69
|
-
rel="noreferrer"
|
|
70
|
-
>
|
|
71
|
-
<h2>
|
|
72
|
-
Configure presetUltramodern
|
|
73
|
-
<img
|
|
74
|
-
className="arrow-right"
|
|
75
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
|
|
76
|
-
alt="Tutorials"
|
|
77
|
-
/>
|
|
78
|
-
</h2>
|
|
79
|
-
<p>Tune the generated defaults in <code className="code">modern.config.ts</code>.</p>
|
|
80
|
-
</a>
|
|
81
|
-
<a
|
|
82
|
-
href="https://github.com/BleedingDev/ultramodern.js/blob/main-ultramodern/packages/toolkit/create/template/.github/workflows/ultramodern-gates.yml.handlebars"
|
|
83
|
-
target="_blank"
|
|
84
|
-
className="card"
|
|
85
|
-
rel="noreferrer"
|
|
86
|
-
>
|
|
87
|
-
<h2>
|
|
88
|
-
Ultramodern Gates
|
|
89
|
-
<img
|
|
90
|
-
className="arrow-right"
|
|
91
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
|
|
92
|
-
alt="Config"
|
|
93
|
-
/>
|
|
94
|
-
</h2>
|
|
95
|
-
<p>The starter includes a PR workflow for <code className="code">ultramodern:check</code> and build.</p>
|
|
96
|
-
</a>
|
|
97
|
-
<a
|
|
98
|
-
href="https://bleedingdev.github.io/ultramodern.js/configure/app/bff/effect.html"
|
|
99
|
-
target="_blank"
|
|
100
|
-
rel="noopener noreferrer"
|
|
101
|
-
className="card"
|
|
102
|
-
>
|
|
103
|
-
<h2>
|
|
104
|
-
BFF + Effect
|
|
105
|
-
<img
|
|
106
|
-
className="arrow-right"
|
|
107
|
-
src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
|
|
108
|
-
alt="Github"
|
|
109
|
-
/>
|
|
110
|
-
</h2>
|
|
111
|
-
<p>Keep Effect as the preferred BFF lane while Hono stays an explicit fallback.</p>
|
|
112
|
-
</a>
|
|
113
|
-
</div>
|
|
114
|
-
</main>
|
|
115
|
-
</div>
|
|
116
|
-
);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export default Index;
|