@bleedingdev/modern-js-create 3.2.0-ultramodern.12 → 3.2.0-ultramodern.120
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 +146 -74
- package/bin/run.js +0 -0
- package/dist/cjs/create-package-root.cjs +65 -0
- package/dist/cjs/index.cjs +498 -0
- package/dist/cjs/locale/en.cjs +94 -0
- package/dist/cjs/locale/index.cjs +50 -0
- package/dist/cjs/locale/zh.cjs +94 -0
- package/dist/cjs/ultramodern-package-source.cjs +135 -0
- package/dist/cjs/ultramodern-workspace.cjs +6797 -0
- package/dist/esm/create-package-root.js +16 -0
- package/dist/esm/index.js +461 -0
- package/dist/esm/locale/en.js +56 -0
- package/dist/esm/locale/index.js +9 -0
- package/dist/esm/locale/zh.js +56 -0
- package/dist/esm/ultramodern-package-source.js +63 -0
- package/dist/esm/ultramodern-workspace.js +6738 -0
- package/dist/esm-node/create-package-root.js +17 -0
- package/dist/esm-node/index.js +462 -0
- package/dist/esm-node/locale/en.js +57 -0
- package/dist/esm-node/locale/index.js +10 -0
- package/dist/esm-node/locale/zh.js +57 -0
- package/dist/esm-node/ultramodern-package-source.js +64 -0
- package/dist/esm-node/ultramodern-workspace.js +6739 -0
- package/dist/types/create-package-root.d.ts +1 -0
- package/dist/types/locale/en.d.ts +7 -7
- package/dist/types/locale/index.d.ts +111 -2
- package/dist/types/locale/zh.d.ts +7 -7
- package/dist/types/ultramodern-package-source.d.ts +28 -0
- package/dist/types/ultramodern-workspace.d.ts +12 -3
- package/package.json +33 -15
- 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 +70 -0
- package/template-workspace/.gitignore.handlebars +5 -0
- package/template-workspace/.mise.toml.handlebars +2 -0
- package/template-workspace/AGENTS.md +43 -11
- package/template-workspace/README.md.handlebars +116 -11
- package/template-workspace/lefthook.yml +24 -0
- package/template-workspace/oxfmt.config.ts +1 -0
- package/template-workspace/oxlint.config.ts +1 -0
- package/template-workspace/pnpm-workspace.yaml +31 -8
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +204 -21
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +370 -0
- package/dist/index.js +0 -2626
- package/template/.agents/skills-lock.json +0 -34
- package/template/.browserslistrc +0 -4
- package/template/.github/workflows/ultramodern-gates.yml.handlebars +0 -30
- package/template/.gitignore.handlebars +0 -30
- package/template/.nvmrc +0 -2
- package/template/AGENTS.md +0 -25
- package/template/README.md +0 -79
- package/template/api/effect/index.ts.handlebars +0 -23
- package/template/api/lambda/hello.ts.handlebars +0 -6
- package/template/config/public/locales/cs/translation.json +0 -39
- package/template/config/public/locales/en/translation.json +0 -39
- package/template/modern.config.ts.handlebars +0 -53
- package/template/oxfmt.config.ts +0 -8
- package/template/oxlint.config.ts +0 -12
- package/template/package.json.handlebars +0 -67
- package/template/postcss.config.mjs.handlebars +0 -6
- package/template/scripts/bootstrap-agent-skills.mjs +0 -95
- package/template/scripts/check-i18n-strings.mjs +0 -83
- package/template/scripts/validate-ultramodern.mjs.handlebars +0 -178
- package/template/shared/effect/api.ts.handlebars +0 -17
- package/template/src/modern-app-env.d.ts +0 -1
- package/template/src/modern.runtime.ts.handlebars +0 -23
- package/template/src/routes/index.css.handlebars +0 -129
- package/template/src/routes/layout.tsx.handlebars +0 -9
- package/template/src/routes/page.tsx.handlebars +0 -155
- package/template/tailwind.config.ts.handlebars +0 -10
- package/template/tsconfig.json +0 -120
- package/template-workspace/scripts/check-i18n-strings.mjs +0 -83
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -433
|
@@ -3,23 +3,78 @@
|
|
|
3
3
|
Generated UltraModern SuperApp workspace.
|
|
4
4
|
|
|
5
5
|
This workspace keeps `presetUltramodern(...)` as the single public
|
|
6
|
-
UltraModern.js 3.0 SuperApp surface and
|
|
7
|
-
starter topology:
|
|
6
|
+
UltraModern.js 3.0 SuperApp surface and starts with an explicit shell:
|
|
8
7
|
|
|
9
|
-
- `apps/shell-super-app` owns shell route assembly
|
|
10
|
-
|
|
11
|
-
- `
|
|
12
|
-
|
|
13
|
-
- `
|
|
14
|
-
- `packages/shared-*` provide placeholders for cross-workspace contracts.
|
|
8
|
+
- `apps/shell-super-app` owns shell route assembly, Module Federation host
|
|
9
|
+
wiring, shared SSR/i18n runtime setup, and the boundary debugger.
|
|
10
|
+
- `packages/shared-*` provide placeholders for cross-workspace contracts,
|
|
11
|
+
design tokens, and Effect API sharing.
|
|
12
|
+
- `verticals/*` is intentionally empty until a real business domain is added.
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
Add a full-stack MicroVertical when the product needs one:
|
|
17
15
|
|
|
18
16
|
```bash
|
|
19
|
-
pnpm
|
|
17
|
+
pnpm dlx @bleedingdev/modern-js-create transportation --vertical
|
|
18
|
+
pnpm dlx @bleedingdev/modern-js-create payments --vertical
|
|
20
19
|
```
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Each added vertical owns its UI/routes, browser-safe Module Federation exposes,
|
|
22
|
+
private-first route metadata, localized URLs, public-route opt-ins, CSS prefix,
|
|
23
|
+
Effect BFF handlers, local API contract, and typed client surface. Server
|
|
24
|
+
handlers and Effect client/contract modules stay out of browser exposes.
|
|
25
|
+
|
|
26
|
+
## Private-First Public Surfaces
|
|
27
|
+
|
|
28
|
+
Generated app routes are private and non-indexable by default. Author route
|
|
29
|
+
metadata in colocated `src/routes/**/route.meta.ts` files; the scaffold
|
|
30
|
+
regenerates `src/routes/ultramodern-route-metadata.ts` as a compatibility
|
|
31
|
+
manifest for config, i18n, public head, and public surface contracts. Private
|
|
32
|
+
app, auth, tenant, dashboard, and internal routes publish no discovery output
|
|
33
|
+
unless route metadata explicitly marks them `public && indexable`. The default
|
|
34
|
+
scaffold therefore emits only a disallowing `robots.txt`; sitemap, web
|
|
35
|
+
manifest, `llms.txt`, API catalog, security.txt, and JSON-LD output stay
|
|
36
|
+
omitted until a safe public route or public docs/help/product surface exists.
|
|
37
|
+
|
|
38
|
+
Public web artifacts are build/deploy outputs generated into `dist/public` and
|
|
39
|
+
`.output/public`, not hand-authored source files under `config/public`. Dynamic
|
|
40
|
+
public routes can expand sitemap entries through route-owned, Node-safe
|
|
41
|
+
`route.sitemap.mjs` providers beside route metadata. The public-surface
|
|
42
|
+
generator discovers those providers for dynamic public routes and still honors
|
|
43
|
+
explicit `routes.publicSurface.contentSources` entries in the generated
|
|
44
|
+
compatibility manifest.
|
|
45
|
+
|
|
46
|
+
Run the scaffold validator before adding business code and after every
|
|
47
|
+
`--vertical` mutation:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
mise install
|
|
51
|
+
pnpm install
|
|
52
|
+
pnpm check
|
|
53
|
+
pnpm build
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Generated CI does not call the local aggregate. It runs format, lint,
|
|
57
|
+
typecheck, skills, i18n boundary validation, contract validation, and build as
|
|
58
|
+
separate matrix jobs so failures are isolated and parallelizable.
|
|
59
|
+
|
|
60
|
+
By default, `pnpm install` also prepares read-only agent reference repositories
|
|
61
|
+
under `repos/` for Effect and UltraModern.js source lookup using squashed git
|
|
62
|
+
subtrees. Disable this setup with
|
|
63
|
+
`ULTRAMODERN_SKIP_AGENT_REPOS=1 pnpm install`, or rerun it
|
|
64
|
+
explicitly with `pnpm agents:refs:install`.
|
|
65
|
+
|
|
66
|
+
Agent skills are prepared during `pnpm install` as a developer convenience.
|
|
67
|
+
External skill repository failures do not block postinstall; strict installation
|
|
68
|
+
is available with `pnpm skills:install`. Use
|
|
69
|
+
`ULTRAMODERN_SKIP_AGENT_SKILLS=1` for a dependency install that avoids external
|
|
70
|
+
skill repositories completely.
|
|
71
|
+
|
|
72
|
+
The topology and ownership metadata are generated under `topology/`. The
|
|
73
|
+
workspace also ships `.github/workflows/ultramodern-workspace-gates.yml` and
|
|
74
|
+
`.github/renovate.json` with read-only workflow permissions, commit-pinned
|
|
75
|
+
actions, frozen installs, StepSecurity audit-mode runner hardening, dependency
|
|
76
|
+
dashboard review, one-day release age, grouped updates, and manual approval for
|
|
77
|
+
major upgrades.
|
|
23
78
|
|
|
24
79
|
Package source metadata is generated at
|
|
25
80
|
`.modernjs/ultramodern-package-source.json`. The default strategy keeps
|
|
@@ -27,3 +82,53 @@ UltraModern.js runtime and tooling packages on `workspace:*` for monorepo
|
|
|
27
82
|
development. To create a workspace that can install those packages outside the
|
|
28
83
|
monorepo, generate with `--ultramodern-package-source install`; generated shared
|
|
29
84
|
packages still use `workspace:*` because they are part of this workspace.
|
|
85
|
+
|
|
86
|
+
## Public URL Environment Variables
|
|
87
|
+
|
|
88
|
+
This workspace's apps no longer bake absolute `http://localhost:<port>` URLs
|
|
89
|
+
into asset configuration. Two environment variables now have distinct roles in
|
|
90
|
+
controlling where assets are served from and where SEO output links point.
|
|
91
|
+
|
|
92
|
+
| Variable | Role | Feeds |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `ULTRAMODERN_PUBLIC_URL_<APP_ID>` | Per-app deployment and asset origin | `output.assetPrefix`, Module Federation remote URLs |
|
|
95
|
+
| `MODERN_PUBLIC_SITE_URL` | Site-wide public origin for SEO output | Canonical, hreflang, sitemap `<loc>`, robots `Sitemap:` |
|
|
96
|
+
|
|
97
|
+
Asset URLs use this precedence: `ULTRAMODERN_PUBLIC_URL_<APP_ID>` →
|
|
98
|
+
`MODERN_PUBLIC_SITE_URL` → inferred workers.dev URL → origin-relative `/`.
|
|
99
|
+
SEO and site origin prefer: `MODERN_PUBLIC_SITE_URL` →
|
|
100
|
+
`ULTRAMODERN_PUBLIC_URL_<APP_ID>` → inferred workers.dev → `http://localhost:<port>`.
|
|
101
|
+
|
|
102
|
+
Without public URLs configured, asset paths are origin-relative (`/`), and the
|
|
103
|
+
dev server uses `dev.assetPrefix: '/'` — so apps work through tunnels and
|
|
104
|
+
reverse proxies (ngrok, cloudflared) without triggering Chrome's Local Network
|
|
105
|
+
Access prompt or mixed-content errors. Shell-only workspaces can set just
|
|
106
|
+
`MODERN_PUBLIC_SITE_URL` for SEO output.
|
|
107
|
+
|
|
108
|
+
## Cloudflare Proof
|
|
109
|
+
|
|
110
|
+
Deploy the generated apps, then pass each deployed app's generated public URL
|
|
111
|
+
env key into the proof step. The proof script reads the generated contract and
|
|
112
|
+
checks the live Worker surface, including public-route sitemap/robots
|
|
113
|
+
consistency, preview noindex behavior, unknown-route status, asset headers,
|
|
114
|
+
byte budgets, and public sourcemap exposure. A shell-only workspace only needs
|
|
115
|
+
the shell URL; added verticals use the same `ULTRAMODERN_PUBLIC_URL_<APP_ID>`
|
|
116
|
+
pattern with hyphens converted to underscores and uppercased.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pnpm cloudflare:deploy
|
|
120
|
+
ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP=https://shell-super-app.example.workers.dev \
|
|
121
|
+
pnpm cloudflare:proof -- --require-public-urls
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Troubleshooting
|
|
125
|
+
|
|
126
|
+
| Symptom | Current check | Owner |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| Package cohort mismatch | Regenerate with one package source strategy, run `mise install`, then rerun `pnpm install` from the activated shell. | Generated workspace package source metadata |
|
|
129
|
+
| Install failure | Check the active Node/pnpm from `mise install`; rerun `pnpm install` after the shell sees the pinned versions. | Toolchain setup |
|
|
130
|
+
| Build failure | Run the matching primitive gate (`pnpm lint`, `pnpm typecheck`, `pnpm i18n:boundaries`, `pnpm contract:check`) before `pnpm build`; fix the owning failure first. | Owning package or generated contract |
|
|
131
|
+
| Missing public URL | Set the env key from `.modernjs/ultramodern-generated-contract.json`, for example `ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP`. | Deployment operator |
|
|
132
|
+
| Cloudflare credentials | Confirm Wrangler credentials before `pnpm cloudflare:deploy`; local checks do not prove live Worker access. | Deployment operator |
|
|
133
|
+
| Asset or CSS 404 | Rebuild with `pnpm build` or `pnpm cloudflare:deploy` and inspect emitted Modern/Rspack asset paths instead of hardcoding CSS URLs. | Framework/runtime asset pipeline |
|
|
134
|
+
| Federation manifest failure | Run the shell and vertical build scripts, then check each deployed `/mf-manifest.json` URL used by the shell. | Module Federation owner |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
pre-commit:
|
|
2
|
+
commands:
|
|
3
|
+
format:
|
|
4
|
+
run: pnpm format
|
|
5
|
+
stage_fixed: true
|
|
6
|
+
lint-fix:
|
|
7
|
+
run: pnpm lint:fix
|
|
8
|
+
stage_fixed: true
|
|
9
|
+
|
|
10
|
+
pre-push:
|
|
11
|
+
parallel: true
|
|
12
|
+
commands:
|
|
13
|
+
format:
|
|
14
|
+
run: pnpm format:check
|
|
15
|
+
lint:
|
|
16
|
+
run: pnpm lint
|
|
17
|
+
typecheck:
|
|
18
|
+
run: pnpm typecheck
|
|
19
|
+
skills:
|
|
20
|
+
run: pnpm skills:check
|
|
21
|
+
i18n-boundaries:
|
|
22
|
+
run: pnpm i18n:boundaries
|
|
23
|
+
contract:
|
|
24
|
+
run: pnpm contract:check
|
|
@@ -1,17 +1,40 @@
|
|
|
1
1
|
packages:
|
|
2
2
|
- apps/*
|
|
3
|
-
-
|
|
4
|
-
- services/*
|
|
3
|
+
- verticals/*
|
|
5
4
|
- packages/*
|
|
6
5
|
|
|
6
|
+
minimumReleaseAge: 1440
|
|
7
|
+
minimumReleaseAgeStrict: true
|
|
8
|
+
minimumReleaseAgeIgnoreMissingTime: false
|
|
9
|
+
minimumReleaseAgeExclude:
|
|
10
|
+
- '@bleedingdev/modern-js-*'
|
|
11
|
+
- '@tanstack/react-router'
|
|
12
|
+
- '@tanstack/router-core'
|
|
13
|
+
- '@typescript/native-preview'
|
|
14
|
+
- '@typescript/native-preview-*'
|
|
15
|
+
- '@types/react'
|
|
16
|
+
trustPolicy: no-downgrade
|
|
17
|
+
trustPolicyIgnoreAfter: 1440
|
|
18
|
+
blockExoticSubdeps: true
|
|
19
|
+
engineStrict: true
|
|
20
|
+
pmOnFail: error
|
|
21
|
+
verifyDepsBeforeRun: error
|
|
22
|
+
strictDepBuilds: true
|
|
23
|
+
|
|
24
|
+
peerDependencyRules:
|
|
25
|
+
allowedVersions:
|
|
26
|
+
react: '>=19.0.0'
|
|
27
|
+
typescript: '>=6.0.0'
|
|
28
|
+
|
|
29
|
+
overrides:
|
|
30
|
+
'@tanstack/react-router': 1.170.15
|
|
31
|
+
node-fetch: '^3.3.2'
|
|
32
|
+
|
|
7
33
|
allowBuilds:
|
|
8
34
|
'@swc/core': true
|
|
9
35
|
core-js: true
|
|
10
36
|
esbuild: true
|
|
37
|
+
lefthook: true
|
|
11
38
|
msgpackr-extract: true
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- '@swc/core'
|
|
15
|
-
- core-js
|
|
16
|
-
- esbuild
|
|
17
|
-
- msgpackr-extract
|
|
39
|
+
sharp: true
|
|
40
|
+
workerd: true
|
|
@@ -7,26 +7,153 @@ const root = process.cwd();
|
|
|
7
7
|
const lockPath = path.join(root, '.agents/skills-lock.json');
|
|
8
8
|
const checkOnly = process.argv.includes('--check');
|
|
9
9
|
const force = process.argv.includes('--force');
|
|
10
|
+
const postinstall = process.argv.includes('--postinstall');
|
|
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
|
+
const skipRequested =
|
|
14
|
+
truthy(process.env.ULTRAMODERN_SKIP_AGENT_SKILLS) ||
|
|
15
|
+
falsy(process.env.ULTRAMODERN_AGENT_SKILLS);
|
|
16
|
+
const cloneTimeoutMs = Number.parseInt(
|
|
17
|
+
process.env.ULTRAMODERN_AGENT_SKILLS_CLONE_TIMEOUT_MS ?? '60000',
|
|
18
|
+
10,
|
|
19
|
+
);
|
|
10
20
|
|
|
11
|
-
const readJson =
|
|
21
|
+
const readJson = filePath => JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
12
22
|
|
|
13
23
|
const run = (command, args, options = {}) =>
|
|
14
24
|
execFileSync(command, args, {
|
|
15
25
|
cwd: options.cwd ?? root,
|
|
16
26
|
encoding: 'utf-8',
|
|
17
27
|
stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
|
|
28
|
+
timeout: options.timeout,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const commandExists = command => {
|
|
32
|
+
try {
|
|
33
|
+
run(command, ['--version'], { stdio: 'ignore' });
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const runShell = script =>
|
|
41
|
+
run('sh', ['-lc', script], {
|
|
42
|
+
stdio: 'inherit',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const installGit = () => {
|
|
46
|
+
if (commandExists('git')) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (commandExists('brew')) {
|
|
51
|
+
run('brew', ['install', 'git'], { stdio: 'inherit' });
|
|
52
|
+
} else if (process.platform === 'linux' && commandExists('apt-get')) {
|
|
53
|
+
const sudo =
|
|
54
|
+
typeof process.getuid === 'function' && process.getuid() === 0
|
|
55
|
+
? ''
|
|
56
|
+
: 'sudo ';
|
|
57
|
+
runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
|
|
58
|
+
} else if (process.platform === 'linux' && commandExists('dnf')) {
|
|
59
|
+
const sudo =
|
|
60
|
+
typeof process.getuid === 'function' && process.getuid() === 0
|
|
61
|
+
? ''
|
|
62
|
+
: 'sudo ';
|
|
63
|
+
runShell(`${sudo}dnf install -y git`);
|
|
64
|
+
} else if (process.platform === 'linux' && commandExists('yum')) {
|
|
65
|
+
const sudo =
|
|
66
|
+
typeof process.getuid === 'function' && process.getuid() === 0
|
|
67
|
+
? ''
|
|
68
|
+
: 'sudo ';
|
|
69
|
+
runShell(`${sudo}yum install -y git`);
|
|
70
|
+
} else if (process.platform === 'linux' && commandExists('apk')) {
|
|
71
|
+
runShell('apk add --no-cache git');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!commandExists('git')) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Git is required for UltraModern setup. Install git and run pnpm skills:install again.',
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const isInsideGitWorkTree = () => {
|
|
82
|
+
try {
|
|
83
|
+
return run('git', ['rev-parse', '--is-inside-work-tree']).trim() === 'true';
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const initializeGitRepository = () => {
|
|
90
|
+
if (isInsideGitWorkTree()) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
run('git', ['init', '-b', 'main'], { stdio: 'inherit' });
|
|
96
|
+
} catch {
|
|
97
|
+
run('git', ['init'], { stdio: 'inherit' });
|
|
98
|
+
run('git', ['branch', '-M', 'main'], { stdio: 'inherit' });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const installLefthook = () => {
|
|
103
|
+
try {
|
|
104
|
+
run('lefthook', ['install'], { stdio: 'inherit' });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.warn(`Unable to install lefthook hooks: ${error.message}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const removeTree = dir =>
|
|
111
|
+
fs.rmSync(dir, {
|
|
112
|
+
force: true,
|
|
113
|
+
maxRetries: 5,
|
|
114
|
+
recursive: true,
|
|
115
|
+
retryDelay: 100,
|
|
18
116
|
});
|
|
19
117
|
|
|
20
118
|
const cloneSource = (source, targetDir) => {
|
|
119
|
+
if (source.commit) {
|
|
120
|
+
run('git', ['init', targetDir], { timeout: 30000 });
|
|
121
|
+
run('git', ['remote', 'add', 'origin', source.repository], {
|
|
122
|
+
cwd: targetDir,
|
|
123
|
+
timeout: 30000,
|
|
124
|
+
});
|
|
125
|
+
run('git', ['fetch', '--depth', '1', '--quiet', 'origin', source.commit], {
|
|
126
|
+
cwd: targetDir,
|
|
127
|
+
timeout: cloneTimeoutMs,
|
|
128
|
+
});
|
|
129
|
+
run(
|
|
130
|
+
'git',
|
|
131
|
+
[
|
|
132
|
+
'-c',
|
|
133
|
+
'advice.detachedHead=false',
|
|
134
|
+
'checkout',
|
|
135
|
+
'--detach',
|
|
136
|
+
'--quiet',
|
|
137
|
+
'FETCH_HEAD',
|
|
138
|
+
],
|
|
139
|
+
{ cwd: targetDir, timeout: 30000 },
|
|
140
|
+
);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
21
144
|
const repo = source.repository.replace(/^https:\/\/github.com\//u, '');
|
|
22
145
|
try {
|
|
23
|
-
run(
|
|
24
|
-
|
|
25
|
-
|
|
146
|
+
run(
|
|
147
|
+
'gh',
|
|
148
|
+
['repo', 'clone', repo, targetDir, '--', '--depth', '1', '--quiet'],
|
|
149
|
+
{ timeout: cloneTimeoutMs },
|
|
150
|
+
);
|
|
26
151
|
} catch {
|
|
27
|
-
run(
|
|
28
|
-
|
|
29
|
-
|
|
152
|
+
run(
|
|
153
|
+
'git',
|
|
154
|
+
['clone', '--depth', '1', '--quiet', source.repository, targetDir],
|
|
155
|
+
{ timeout: cloneTimeoutMs },
|
|
156
|
+
);
|
|
30
157
|
}
|
|
31
158
|
};
|
|
32
159
|
|
|
@@ -37,7 +164,9 @@ const resolveSkillDir = (sourceRoot, skillName) => {
|
|
|
37
164
|
path.join(sourceRoot, 'skills', 'engineering', skillName),
|
|
38
165
|
path.join(sourceRoot, 'skills', 'productivity', skillName),
|
|
39
166
|
];
|
|
40
|
-
return candidates.find(
|
|
167
|
+
return candidates.find(candidate =>
|
|
168
|
+
fs.existsSync(path.join(candidate, 'SKILL.md')),
|
|
169
|
+
);
|
|
41
170
|
};
|
|
42
171
|
|
|
43
172
|
if (!fs.existsSync(lockPath)) {
|
|
@@ -47,36 +176,88 @@ if (!fs.existsSync(lockPath)) {
|
|
|
47
176
|
|
|
48
177
|
const lock = readJson(lockPath);
|
|
49
178
|
const installDir = path.join(root, lock.installDir ?? '.agents/skills');
|
|
50
|
-
const
|
|
51
|
-
|
|
179
|
+
const sources = lock.sources ?? [];
|
|
180
|
+
const requiredCloneSources = sources.filter(
|
|
181
|
+
source => source.install === 'clone',
|
|
182
|
+
);
|
|
183
|
+
const optionalCloneSources = sources.filter(
|
|
184
|
+
source => source.install === 'clone-if-authorized',
|
|
185
|
+
);
|
|
186
|
+
const requiredSkills = [
|
|
187
|
+
...(lock.baseline ?? []),
|
|
188
|
+
...requiredCloneSources.flatMap(source => source.baseline ?? []),
|
|
189
|
+
].filter(
|
|
190
|
+
(skill, index, skills) =>
|
|
191
|
+
skills.findIndex(candidate => candidate.name === skill.name) === index,
|
|
52
192
|
);
|
|
53
193
|
|
|
194
|
+
if (skipRequested) {
|
|
195
|
+
const reason = 'agent skills bootstrap skipped by environment';
|
|
196
|
+
if (checkOnly) {
|
|
197
|
+
console.log(reason);
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
console.log(reason);
|
|
201
|
+
installLefthook();
|
|
202
|
+
process.exit(0);
|
|
203
|
+
}
|
|
204
|
+
|
|
54
205
|
if (checkOnly) {
|
|
55
|
-
const
|
|
206
|
+
const missingRequired = requiredSkills
|
|
207
|
+
.map(skill => skill.name)
|
|
208
|
+
.filter(
|
|
209
|
+
skillName => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
|
|
210
|
+
);
|
|
211
|
+
const missingOptional = optionalCloneSources.flatMap(source =>
|
|
56
212
|
(source.baseline ?? [])
|
|
57
|
-
.map(
|
|
58
|
-
.filter(
|
|
213
|
+
.map(skill => skill.name)
|
|
214
|
+
.filter(
|
|
215
|
+
skillName =>
|
|
216
|
+
!fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
|
|
217
|
+
),
|
|
59
218
|
);
|
|
60
|
-
|
|
219
|
+
|
|
220
|
+
if (missingRequired.length > 0) {
|
|
221
|
+
console.error(
|
|
222
|
+
`Required agent skills not installed: ${missingRequired.join(', ')}. Run pnpm skills:install.`,
|
|
223
|
+
);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (missingOptional.length > 0) {
|
|
61
228
|
console.warn(
|
|
62
|
-
`Private skills not installed: ${
|
|
229
|
+
`Private skills not installed: ${missingOptional.join(', ')}. Run pnpm skills:install if you have access.`,
|
|
63
230
|
);
|
|
64
231
|
} else {
|
|
65
|
-
console.log('
|
|
232
|
+
console.log('Required and private agent skills are installed.');
|
|
233
|
+
process.exit(0);
|
|
66
234
|
}
|
|
235
|
+
console.log('Required agent skills are installed.');
|
|
67
236
|
process.exit(0);
|
|
68
237
|
}
|
|
69
238
|
|
|
70
239
|
fs.mkdirSync(installDir, { recursive: true });
|
|
240
|
+
installGit();
|
|
241
|
+
initializeGitRepository();
|
|
71
242
|
|
|
72
|
-
for (const source of
|
|
243
|
+
for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
|
|
73
244
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ultramodern-skills-'));
|
|
74
245
|
try {
|
|
75
|
-
|
|
246
|
+
try {
|
|
247
|
+
cloneSource(source, tempDir);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (source.install === 'clone-if-authorized' || postinstall) {
|
|
250
|
+
console.warn(`Skipping ${source.repository}; ${error.message}`);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
76
255
|
for (const skill of source.baseline ?? []) {
|
|
77
256
|
const sourceSkillDir = resolveSkillDir(tempDir, skill.name);
|
|
78
257
|
if (!sourceSkillDir) {
|
|
79
|
-
throw new Error(
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Skill ${skill.name} not found in ${source.repository}`,
|
|
260
|
+
);
|
|
80
261
|
}
|
|
81
262
|
const targetSkillDir = path.join(installDir, skill.name);
|
|
82
263
|
if (fs.existsSync(targetSkillDir)) {
|
|
@@ -84,12 +265,14 @@ for (const source of privateSources) {
|
|
|
84
265
|
console.log(`Skipping existing ${skill.name}`);
|
|
85
266
|
continue;
|
|
86
267
|
}
|
|
87
|
-
|
|
268
|
+
removeTree(targetSkillDir);
|
|
88
269
|
}
|
|
89
270
|
fs.cpSync(sourceSkillDir, targetSkillDir, { recursive: true });
|
|
90
271
|
console.log(`Installed ${skill.name}`);
|
|
91
272
|
}
|
|
92
273
|
} finally {
|
|
93
|
-
|
|
274
|
+
removeTree(tempDir);
|
|
94
275
|
}
|
|
95
276
|
}
|
|
277
|
+
|
|
278
|
+
installLefthook();
|