@bleedingdev/modern-js-create 3.2.0-ultramodern.6 → 3.2.0-ultramodern.60
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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'oxfmt';
|
|
2
|
+
import ultracite from 'ultracite/oxfmt';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
extends: [ultracite],
|
|
6
|
+
ignorePatterns: [
|
|
7
|
+
'.agents',
|
|
8
|
+
'**/*.json',
|
|
9
|
+
'dist',
|
|
10
|
+
'node_modules',
|
|
11
|
+
'.modern',
|
|
12
|
+
'.modernjs',
|
|
13
|
+
'**/routeTree.gen.ts',
|
|
14
|
+
],
|
|
15
|
+
singleQuote: true,
|
|
16
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
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: [
|
|
12
|
+
'.agents',
|
|
13
|
+
'dist',
|
|
14
|
+
'node_modules',
|
|
15
|
+
'.modern',
|
|
16
|
+
'.modernjs',
|
|
17
|
+
'**/routeTree.gen.ts',
|
|
18
|
+
],
|
|
19
|
+
});
|
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
packages:
|
|
2
2
|
- apps/*
|
|
3
|
-
-
|
|
4
|
-
- services/*
|
|
3
|
+
- verticals/*
|
|
5
4
|
- packages/*
|
|
6
5
|
|
|
6
|
+
minimumReleaseAge: 0
|
|
7
|
+
blockExoticSubdeps: true
|
|
8
|
+
engineStrict: true
|
|
9
|
+
pmOnFail: error
|
|
10
|
+
verifyDepsBeforeRun: error
|
|
11
|
+
strictDepBuilds: true
|
|
12
|
+
|
|
13
|
+
peerDependencyRules:
|
|
14
|
+
allowedVersions:
|
|
15
|
+
react: '>=19.0.0'
|
|
16
|
+
typescript: '>=6.0.0'
|
|
17
|
+
|
|
18
|
+
overrides:
|
|
19
|
+
'@tanstack/react-router': 1.170.8
|
|
20
|
+
node-fetch: '^3.3.2'
|
|
21
|
+
|
|
7
22
|
allowBuilds:
|
|
8
|
-
'@biomejs/biome': true
|
|
9
23
|
'@swc/core': true
|
|
10
24
|
core-js: true
|
|
11
25
|
esbuild: true
|
|
26
|
+
lefthook: true
|
|
12
27
|
msgpackr-extract: true
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- '@biomejs/biome'
|
|
16
|
-
- '@swc/core'
|
|
17
|
-
- core-js
|
|
18
|
-
- esbuild
|
|
19
|
-
- msgpackr-extract
|
|
28
|
+
sharp: true
|
|
29
|
+
workerd: true
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
if (source.commit) {
|
|
40
|
+
try {
|
|
41
|
+
run('git', ['checkout', source.commit], {
|
|
42
|
+
cwd: targetDir,
|
|
43
|
+
stdio: 'inherit',
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
run('git', ['fetch', '--depth', '1', 'origin', source.commit], {
|
|
47
|
+
cwd: targetDir,
|
|
48
|
+
stdio: 'inherit',
|
|
49
|
+
});
|
|
50
|
+
run('git', ['checkout', source.commit], {
|
|
51
|
+
cwd: targetDir,
|
|
52
|
+
stdio: 'inherit',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const resolveSkillDir = (sourceRoot, skillName) => {
|
|
59
|
+
const candidates = [
|
|
60
|
+
path.join(sourceRoot, skillName),
|
|
61
|
+
path.join(sourceRoot, 'skills', skillName),
|
|
62
|
+
path.join(sourceRoot, 'skills', 'engineering', skillName),
|
|
63
|
+
path.join(sourceRoot, 'skills', 'productivity', skillName),
|
|
64
|
+
];
|
|
65
|
+
return candidates.find(candidate =>
|
|
66
|
+
fs.existsSync(path.join(candidate, 'SKILL.md')),
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(lockPath)) {
|
|
71
|
+
console.error('Missing .agents/skills-lock.json');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const lock = readJson(lockPath);
|
|
76
|
+
const installDir = path.join(root, lock.installDir ?? '.agents/skills');
|
|
77
|
+
const sources = lock.sources ?? [];
|
|
78
|
+
const requiredCloneSources = sources.filter(
|
|
79
|
+
source => source.install === 'clone',
|
|
80
|
+
);
|
|
81
|
+
const optionalCloneSources = sources.filter(
|
|
82
|
+
source => source.install === 'clone-if-authorized',
|
|
83
|
+
);
|
|
84
|
+
const requiredSkills = [
|
|
85
|
+
...(lock.baseline ?? []),
|
|
86
|
+
...requiredCloneSources.flatMap(source => source.baseline ?? []),
|
|
87
|
+
].filter(
|
|
88
|
+
(skill, index, skills) =>
|
|
89
|
+
skills.findIndex(candidate => candidate.name === skill.name) === index,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (checkOnly) {
|
|
93
|
+
const missingRequired = requiredSkills
|
|
94
|
+
.map(skill => skill.name)
|
|
95
|
+
.filter(
|
|
96
|
+
skillName => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
|
|
97
|
+
);
|
|
98
|
+
const missingOptional = optionalCloneSources.flatMap(source =>
|
|
99
|
+
(source.baseline ?? [])
|
|
100
|
+
.map(skill => skill.name)
|
|
101
|
+
.filter(
|
|
102
|
+
skillName =>
|
|
103
|
+
!fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (missingRequired.length > 0) {
|
|
108
|
+
console.error(
|
|
109
|
+
`Required agent skills not installed: ${missingRequired.join(', ')}. Run pnpm skills:install.`,
|
|
110
|
+
);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (missingOptional.length > 0) {
|
|
115
|
+
console.warn(
|
|
116
|
+
`Private skills not installed: ${missingOptional.join(', ')}. Run pnpm skills:install if you have access.`,
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
console.log('Required and private agent skills are installed.');
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
console.log('Required agent skills are installed.');
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
127
|
+
|
|
128
|
+
for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
|
|
129
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ultramodern-skills-'));
|
|
130
|
+
try {
|
|
131
|
+
try {
|
|
132
|
+
cloneSource(source, tempDir);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (source.install === 'clone-if-authorized') {
|
|
135
|
+
console.warn(
|
|
136
|
+
`Skipping ${source.repository}; current developer may not have access.`,
|
|
137
|
+
);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
for (const skill of source.baseline ?? []) {
|
|
143
|
+
const sourceSkillDir = resolveSkillDir(tempDir, skill.name);
|
|
144
|
+
if (!sourceSkillDir) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Skill ${skill.name} not found in ${source.repository}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const targetSkillDir = path.join(installDir, skill.name);
|
|
150
|
+
if (fs.existsSync(targetSkillDir)) {
|
|
151
|
+
if (!force) {
|
|
152
|
+
console.log(`Skipping existing ${skill.name}`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
removeTree(targetSkillDir);
|
|
156
|
+
}
|
|
157
|
+
fs.cpSync(sourceSkillDir, targetSkillDir, { recursive: true });
|
|
158
|
+
console.log(`Installed ${skill.name}`);
|
|
159
|
+
}
|
|
160
|
+
} finally {
|
|
161
|
+
removeTree(tempDir);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -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
|
+
}
|
package/template/biome.json
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"root": false,
|
|
3
|
-
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
|
4
|
-
"vcs": {
|
|
5
|
-
"enabled": true,
|
|
6
|
-
"defaultBranch": "main",
|
|
7
|
-
"clientKind": "git",
|
|
8
|
-
"useIgnoreFile": true
|
|
9
|
-
},
|
|
10
|
-
"formatter": {
|
|
11
|
-
"enabled": true,
|
|
12
|
-
"indentStyle": "space"
|
|
13
|
-
},
|
|
14
|
-
"javascript": {
|
|
15
|
-
"formatter": {
|
|
16
|
-
"quoteStyle": "single",
|
|
17
|
-
"arrowParentheses": "asNeeded",
|
|
18
|
-
"jsxQuoteStyle": "double",
|
|
19
|
-
"lineWidth": 80
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"linter": {
|
|
23
|
-
"enabled": true,
|
|
24
|
-
"rules": {
|
|
25
|
-
"recommended": true,
|
|
26
|
-
"suspicious": {
|
|
27
|
-
"noDuplicateFontNames": "off"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
32
|
-
"files": {
|
|
33
|
-
"ignoreUnknown": true,
|
|
34
|
-
"includes": [
|
|
35
|
-
"**",
|
|
36
|
-
"!**/.vscode/**/*",
|
|
37
|
-
"!**/node_modules/**/*",
|
|
38
|
-
"!**/dist/**/*"
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
}
|