@icebreakers/monorepo-templates 2.1.0 → 2.2.0
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/assets/.agents/skills/repoctl/SKILL.md +2 -0
- package/assets/.agents/skills/repoctl/references/commands.md +224 -13
- package/assets/.agents/skills/repoctl/references/config.md +70 -6
- package/assets/.agents/skills/repoctl/references/dependencies.md +37 -0
- package/assets/.agents/skills/repoctl/references/knip.md +34 -0
- package/assets/.agents/skills/repoctl/references/templates.md +23 -0
- package/assets/.github/workflows/ci.yml +10 -10
- package/assets/package.json +5 -9
- package/assets/pnpm-workspace.yaml +2 -1
- package/assets/repoctl.config.ts +1 -1
- package/assets/turbo.json +2 -1
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +281 -1
- package/dist/index.mjs +1137 -74
- package/dist/{prepare-2VvFwYDz.mjs → prepare-BYNSxvoB.mjs} +76 -4
- package/package.json +2 -1
- package/template-data.mjs +24 -0
- package/templates/client/package.json +1 -1
- package/templates/next/README.md +31 -0
- package/templates/next/eslint.config.js +5 -0
- package/templates/next/gitignore +7 -0
- package/templates/next/next.config.ts +5 -0
- package/templates/next/package.json +36 -0
- package/templates/next/src/app/api/health/route.ts +5 -0
- package/templates/next/src/app/counter.tsx +19 -0
- package/templates/next/src/app/favicon.ico +0 -0
- package/templates/next/src/app/globals.css +109 -0
- package/templates/next/src/app/layout.tsx +17 -0
- package/templates/next/src/app/page.tsx +16 -0
- package/templates/next/stylelint.config.js +3 -0
- package/templates/next/test/counter.test.tsx +22 -0
- package/templates/next/tsconfig.json +40 -0
- package/templates/next/turbo.json +24 -0
- package/templates/next/vitest.config.js +9 -0
- package/templates/react-lib/LICENSE +21 -0
- package/templates/react-lib/README.md +52 -0
- package/templates/react-lib/eslint.config.js +5 -0
- package/templates/react-lib/gitignore +4 -0
- package/templates/react-lib/package.json +48 -0
- package/templates/react-lib/src/counter.tsx +28 -0
- package/templates/react-lib/src/index.ts +2 -0
- package/templates/react-lib/src/style.css +36 -0
- package/templates/react-lib/stylelint.config.js +3 -0
- package/templates/react-lib/test/counter.test.tsx +28 -0
- package/templates/react-lib/test-d/index.test-d.ts +11 -0
- package/templates/react-lib/tsconfig.app.json +15 -0
- package/templates/react-lib/tsconfig.build.json +12 -0
- package/templates/react-lib/tsconfig.json +4 -0
- package/templates/react-lib/tsconfig.node.json +11 -0
- package/templates/react-lib/vite.config.ts +22 -0
- package/templates/react-lib/vitest.config.ts +5 -0
- package/templates/react-vite/README.md +27 -0
- package/templates/react-vite/eslint.config.js +5 -0
- package/templates/react-vite/gitignore +6 -0
- package/templates/react-vite/index.html +13 -0
- package/templates/react-vite/package.json +36 -0
- package/templates/react-vite/src/app.tsx +22 -0
- package/templates/react-vite/src/main.tsx +15 -0
- package/templates/react-vite/src/style.css +95 -0
- package/templates/react-vite/stylelint.config.js +3 -0
- package/templates/react-vite/test/app.test.tsx +22 -0
- package/templates/react-vite/tsconfig.app.json +26 -0
- package/templates/react-vite/tsconfig.json +11 -0
- package/templates/react-vite/tsconfig.node.json +18 -0
- package/templates/react-vite/vite.config.ts +6 -0
- package/templates/react-vite/vitest.config.ts +9 -0
- package/templates/vitepress/.vitepress/navigation/routes.ts +7 -0
- package/templates/vitepress/reference/artifacts.md +51 -0
- package/templates/vitepress/reference/commands.md +149 -0
- package/templates/vitepress/reference/config.md +101 -7
- package/templates/vitepress/reference/dependencies.md +116 -0
- package/templates/vitepress/reference/dependency-admission.md +67 -0
- package/templates/vitepress/reference/devcontainer.md +25 -0
- package/templates/vitepress/reference/index.md +1 -0
- package/templates/vitepress/reference/install-security.md +51 -0
- package/templates/vitepress/reference/knip.md +55 -0
- package/templates/vitepress/reference/move.md +39 -0
- package/templates/vitepress/reference/organization-presets.md +89 -0
- package/templates/vitepress/reference/removal.md +42 -0
- package/templates/vitepress/reference/template-assets.md +39 -0
- package/templates/vitepress/reference/templates.md +372 -9
- package/templates/vitepress/start/diagnose.md +19 -2
- package/templates/vitepress/tasks/checks.md +149 -1
- package/templates/vitepress/tasks/ci.md +93 -1
- package/templates/vitepress/tasks/release.md +72 -6
- package/templates/vitepress/zh/reference/artifacts.md +51 -0
- package/templates/vitepress/zh/reference/commands.md +148 -0
- package/templates/vitepress/zh/reference/config.md +94 -2
- package/templates/vitepress/zh/reference/dependencies.md +116 -0
- package/templates/vitepress/zh/reference/dependency-admission.md +67 -0
- package/templates/vitepress/zh/reference/devcontainer.md +25 -0
- package/templates/vitepress/zh/reference/index.md +1 -0
- package/templates/vitepress/zh/reference/install-security.md +51 -0
- package/templates/vitepress/zh/reference/knip.md +55 -0
- package/templates/vitepress/zh/reference/move.md +39 -0
- package/templates/vitepress/zh/reference/organization-presets.md +89 -0
- package/templates/vitepress/zh/reference/removal.md +42 -0
- package/templates/vitepress/zh/reference/template-assets.md +19 -4
- package/templates/vitepress/zh/reference/templates.md +352 -9
- package/templates/vitepress/zh/start/diagnose.md +24 -13
- package/templates/vitepress/zh/tasks/checks.md +150 -0
- package/templates/vitepress/zh/tasks/ci.md +93 -1
- package/templates/vitepress/zh/tasks/release.md +72 -6
- package/templates/vue-lib/package.json +1 -1
|
@@ -73,6 +73,14 @@ const templateChoices = [
|
|
|
73
73
|
category: "app",
|
|
74
74
|
description: "Vue 3 + Hono 的前后端一体应用模板"
|
|
75
75
|
},
|
|
76
|
+
{
|
|
77
|
+
key: "react-vite",
|
|
78
|
+
label: "React Vite 应用",
|
|
79
|
+
source: "react-vite",
|
|
80
|
+
target: "apps/react-vite",
|
|
81
|
+
category: "app",
|
|
82
|
+
description: "React + Vite + TypeScript 单页应用,包含交互测试与共享工程规范"
|
|
83
|
+
},
|
|
76
84
|
{
|
|
77
85
|
key: "hono-server",
|
|
78
86
|
label: "hono 模板",
|
|
@@ -104,6 +112,22 @@ const templateChoices = [
|
|
|
104
112
|
target: "apps/docs",
|
|
105
113
|
category: "docs",
|
|
106
114
|
description: "默认文档模板:Nimbus + Astro,中英双语与 AI 文档入口"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
key: "react-lib",
|
|
118
|
+
label: "React 组件库",
|
|
119
|
+
source: "react-lib",
|
|
120
|
+
target: "packages/react-lib",
|
|
121
|
+
category: "library",
|
|
122
|
+
description: "带类型声明、显式 CSS 出口和发布消费验证的 React 组件库"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
key: "next",
|
|
126
|
+
label: "Next.js App Router 应用",
|
|
127
|
+
source: "next",
|
|
128
|
+
target: "apps/next",
|
|
129
|
+
category: "app",
|
|
130
|
+
description: "Next.js + TypeScript SSR 应用,包含客户端交互、健康接口与工作区库集成"
|
|
107
131
|
}
|
|
108
132
|
];
|
|
109
133
|
//#endregion
|
|
@@ -154,13 +178,15 @@ const skipDirs = /* @__PURE__ */ new Set([
|
|
|
154
178
|
".vue-global-types",
|
|
155
179
|
".wrangler",
|
|
156
180
|
".astro",
|
|
157
|
-
".nimbus"
|
|
181
|
+
".nimbus",
|
|
182
|
+
".next"
|
|
158
183
|
]);
|
|
159
184
|
const skipFiles = /* @__PURE__ */ new Set([
|
|
160
185
|
".DS_Store",
|
|
161
186
|
"CHANGELOG.md",
|
|
162
187
|
"route-map.d.ts",
|
|
163
|
-
"worker-configuration.d.ts"
|
|
188
|
+
"worker-configuration.d.ts",
|
|
189
|
+
"next-env.d.ts"
|
|
164
190
|
]);
|
|
165
191
|
function shouldSkipTemplatePath(rootDir, targetPath) {
|
|
166
192
|
const basename = path.basename(targetPath);
|
|
@@ -179,6 +205,14 @@ function createTemplateCopyFilter(rootDir) {
|
|
|
179
205
|
}
|
|
180
206
|
//#endregion
|
|
181
207
|
//#region src/prepare/published.ts
|
|
208
|
+
const sourceScriptNames = /* @__PURE__ */ new Set([
|
|
209
|
+
"tooling:build",
|
|
210
|
+
"check:no-tracked-build-artifacts",
|
|
211
|
+
"check:workflows",
|
|
212
|
+
"test:dev-scenarios",
|
|
213
|
+
"test:worker-types"
|
|
214
|
+
]);
|
|
215
|
+
const isSourceOnlyScript = (name) => name.startsWith("dev:") || sourceScriptNames.has(name) || name.startsWith("test:packaged-");
|
|
182
216
|
function sanitizePublishedManifestContent(content) {
|
|
183
217
|
const manifest = JSON.parse(content);
|
|
184
218
|
if (manifest.devDependencies) {
|
|
@@ -187,14 +221,17 @@ function sanitizePublishedManifestContent(content) {
|
|
|
187
221
|
"@icebreakers/eslint-config",
|
|
188
222
|
"@icebreakers/monorepo",
|
|
189
223
|
"@icebreakers/stylelint-config",
|
|
224
|
+
"make-fetch-happen",
|
|
190
225
|
"playwright"
|
|
191
226
|
]) delete manifest.devDependencies[name];
|
|
192
227
|
if (manifest.devDependencies["repoctl"]?.startsWith("workspace:")) manifest.devDependencies["repoctl"] = "latest";
|
|
193
228
|
}
|
|
194
229
|
if (manifest.scripts) {
|
|
195
|
-
for (const name of Object.keys(manifest.scripts)) if (
|
|
230
|
+
for (const name of Object.keys(manifest.scripts)) if (isSourceOnlyScript(name)) delete manifest.scripts[name];
|
|
196
231
|
manifest.scripts["dev"] = "turbo run dev --concurrency=20";
|
|
232
|
+
if (manifest.scripts["lint"]) manifest.scripts["lint"] = manifest.scripts["lint"].replace(/^pnpm run tooling:build && /u, "");
|
|
197
233
|
if (manifest.scripts["test"]) manifest.scripts["test"] = manifest.scripts["test"].replace(" && pnpm test:dev-scenarios", "");
|
|
234
|
+
for (const name of ["test", "test:dev"]) if (manifest.scripts[name]) manifest.scripts[name] = manifest.scripts[name].replace("pnpm check:no-tracked-build-artifacts && ", "");
|
|
198
235
|
}
|
|
199
236
|
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
200
237
|
}
|
|
@@ -256,6 +293,40 @@ function sanitizePublishedWorkspaceContent(content) {
|
|
|
256
293
|
return changed ? YAML.stringify(workspace) : content;
|
|
257
294
|
}
|
|
258
295
|
//#endregion
|
|
296
|
+
//#region src/prepare/workflows.ts
|
|
297
|
+
function sourceCommand(command) {
|
|
298
|
+
if (/[;&|`$<>]/u.test(command)) return false;
|
|
299
|
+
const script = /^pnpm\s+(?:--[\w-]+(?:=[\w./:-]+)?\s+)*(?:run\s+(?:--[\w-]+(?:=[\w./:-]+)?\s+)*)?([\w:-]+)(?:\s|$)/u.exec(command)?.[1];
|
|
300
|
+
return script !== void 0 && isSourceOnlyScript(script) || /^pnpm\s+exec\s+playwright\s+install(?:\s|$)/u.test(command);
|
|
301
|
+
}
|
|
302
|
+
function mentionsSourceCommand(command) {
|
|
303
|
+
return command.split(/[^\w:-]+/u).some(isSourceOnlyScript) || /\bplaywright\s+install\b/u.test(command);
|
|
304
|
+
}
|
|
305
|
+
/** Filter YAML steps, so neighboring steps and additional fields cannot escape publication rules. */
|
|
306
|
+
function sanitizePublishedCiWorkflowContent(content) {
|
|
307
|
+
const document = YAML.parseDocument(content);
|
|
308
|
+
if (document.errors.length) throw new Error("Cannot publish an invalid CI workflow.");
|
|
309
|
+
const jobs = document.get("jobs", true);
|
|
310
|
+
if (!YAML.isMap(jobs)) return content;
|
|
311
|
+
let changed = false;
|
|
312
|
+
for (const { value: job } of jobs.items) {
|
|
313
|
+
if (!YAML.isMap(job)) continue;
|
|
314
|
+
const steps = job.get("steps", true);
|
|
315
|
+
if (!YAML.isSeq(steps)) continue;
|
|
316
|
+
for (let index = steps.items.length - 1; index >= 0; index--) {
|
|
317
|
+
const step = steps.items[index];
|
|
318
|
+
const run = YAML.isMap(step) ? step.get("run") : void 0;
|
|
319
|
+
if (typeof run !== "string") continue;
|
|
320
|
+
const commands = run.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
321
|
+
if (!commands.some(mentionsSourceCommand)) continue;
|
|
322
|
+
if (!commands.every(sourceCommand)) throw new Error("Move source-only commands into separate CI steps before publishing templates.");
|
|
323
|
+
steps.delete(index);
|
|
324
|
+
changed = true;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return changed ? document.toString({ lineWidth: 0 }) : content;
|
|
328
|
+
}
|
|
329
|
+
//#endregion
|
|
259
330
|
//#region src/prepare/index.ts
|
|
260
331
|
const huskySkippedEntryPattern = /[\\/]_$/;
|
|
261
332
|
function toPublishedAssetPath(target) {
|
|
@@ -377,10 +448,11 @@ async function removeSourceRepoReleaseToolingBuildStep() {
|
|
|
377
448
|
if (nextContent !== content) await fs.writeFile(releaseWorkflowPath, nextContent, "utf8");
|
|
378
449
|
}
|
|
379
450
|
async function removeSourceRepoChecks() {
|
|
451
|
+
await fs.rm(path$1.join(assetsDir, ".github/workflows/devcontainer.yml"), { force: true });
|
|
380
452
|
const workflowPath = path$1.join(assetsDir, ".github/workflows/ci.yml");
|
|
381
453
|
if (await pathExists(workflowPath)) {
|
|
382
454
|
const workflow = await fs.readFile(workflowPath, "utf8");
|
|
383
|
-
await fs.writeFile(workflowPath, workflow
|
|
455
|
+
await fs.writeFile(workflowPath, sanitizePublishedCiWorkflowContent(workflow));
|
|
384
456
|
}
|
|
385
457
|
const manifestPath = path$1.join(assetsDir, "package.json");
|
|
386
458
|
if (await pathExists(manifestPath)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icebreakers/monorepo-templates",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "Built-in project templates and managed workspace assets for repoctl",
|
|
6
6
|
"author": "ice breaker <hi@sonofmagic.top>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@inquirer/checkbox": "^5.2.5",
|
|
50
50
|
"@inquirer/input": "^5.1.6",
|
|
51
|
+
"@inquirer/password": "^5.2.2",
|
|
51
52
|
"@inquirer/select": "^5.2.5",
|
|
52
53
|
"commander": "^15.0.0",
|
|
53
54
|
"nanoexec": "^1.1.0",
|
package/template-data.mjs
CHANGED
|
@@ -23,6 +23,14 @@ export const templateChoices = [
|
|
|
23
23
|
category: 'app',
|
|
24
24
|
description: 'Vue 3 + Hono 的前后端一体应用模板',
|
|
25
25
|
},
|
|
26
|
+
{
|
|
27
|
+
key: 'react-vite',
|
|
28
|
+
label: 'React Vite 应用',
|
|
29
|
+
source: 'react-vite',
|
|
30
|
+
target: 'apps/react-vite',
|
|
31
|
+
category: 'app',
|
|
32
|
+
description: 'React + Vite + TypeScript 单页应用,包含交互测试与共享工程规范',
|
|
33
|
+
},
|
|
26
34
|
{
|
|
27
35
|
key: 'hono-server',
|
|
28
36
|
label: 'hono 模板',
|
|
@@ -55,4 +63,20 @@ export const templateChoices = [
|
|
|
55
63
|
category: 'docs',
|
|
56
64
|
description: '默认文档模板:Nimbus + Astro,中英双语与 AI 文档入口',
|
|
57
65
|
},
|
|
66
|
+
{
|
|
67
|
+
key: 'react-lib',
|
|
68
|
+
label: 'React 组件库',
|
|
69
|
+
source: 'react-lib',
|
|
70
|
+
target: 'packages/react-lib',
|
|
71
|
+
category: 'library',
|
|
72
|
+
description: '带类型声明、显式 CSS 出口和发布消费验证的 React 组件库',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: 'next',
|
|
76
|
+
label: 'Next.js App Router 应用',
|
|
77
|
+
source: 'next',
|
|
78
|
+
target: 'apps/next',
|
|
79
|
+
category: 'app',
|
|
80
|
+
description: 'Next.js + TypeScript SSR 应用,包含客户端交互、健康接口与工作区库集成',
|
|
81
|
+
},
|
|
58
82
|
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Next.js App Router workspace
|
|
2
|
+
|
|
3
|
+
This TypeScript template follows `create-next-app@16.3.8`'s App Router layout and uses the shared repoctl tooling. It includes a server page, a client counter and `GET /api/health`. It requires Node 22.13 or newer and shares the workspace's pnpm version and lockfile.
|
|
4
|
+
|
|
5
|
+
Run these from the generated workspace root:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
corepack enable
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm build
|
|
11
|
+
pnpm lint
|
|
12
|
+
pnpm typecheck
|
|
13
|
+
pnpm test
|
|
14
|
+
pnpm --dir apps/next start
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For development, run `pnpm --dir apps/next dev`. When created with `repo new portal --template next`, use `apps/portal` instead. Lint runs ESLint and Stylelint independently of Next build. `typecheck` runs `next typegen` before `tsc --noEmit`, so route types exist even before the first production build. Unit tests cover client state; the repoctl source repository also tests the built server and browser using actual published tarballs.
|
|
18
|
+
|
|
19
|
+
## Internal packages
|
|
20
|
+
|
|
21
|
+
For a compiled workspace library, add `"@acme/shared": "workspace:*"` to dependencies and import its public package name. The library should expose its built JavaScript and declaration files through `exports`. Turbo's inherited `^build` builds the library first; do not reach into another package's source via relative imports.
|
|
22
|
+
|
|
23
|
+
To consume an intentionally source-exporting TypeScript package, add its package name to `transpilePackages` in `next.config.ts`. Its exports and server/client compatibility must support the target environment. `transpilePackages` does not generate that library's standalone declaration files, and server-only code must not enter a client component. The packaged acceptance test covers both compiled and source-exporting internal packages.
|
|
24
|
+
|
|
25
|
+
## Cache and runtime boundaries
|
|
26
|
+
|
|
27
|
+
The local `turbo.json` includes all authored app inputs and caches `.next/**` except `.next/cache/**`. Development and production server tasks are persistent and uncached; type generation is uncached to keep Next's generated route declarations available. `.next`, `next-env.d.ts`, and TypeScript build info are generated locally and never shipped as template inputs.
|
|
28
|
+
|
|
29
|
+
No external font download is needed to build this starter. Configure environment values and production hosting for your application separately; no authentication, database or deployment platform is included.
|
|
30
|
+
|
|
31
|
+
References: [create-next-app](https://nextjs.org/docs/app/api-reference/cli/create-next-app), [Next CLI and typegen](https://nextjs.org/docs/app/api-reference/cli/next), [transpilePackages](https://nextjs.org/docs/app/api-reference/config/next-config-js/transpilePackages).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@icebreakers/next-template",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"description": "Internal Next.js App Router and TypeScript application template shipped with repoctl",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22.13.0"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "next dev",
|
|
13
|
+
"build": "next build",
|
|
14
|
+
"start": "next start",
|
|
15
|
+
"lint": "eslint . && stylelint \"src/**/*.css\"",
|
|
16
|
+
"lint:fix": "eslint . --fix && stylelint \"src/**/*.css\" --fix",
|
|
17
|
+
"typecheck": "next typegen && tsc --noEmit",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:dev": "vitest"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"next": "16.3.8",
|
|
23
|
+
"react": "^19.3.0",
|
|
24
|
+
"react-dom": "^19.3.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@testing-library/dom": "^10.4.2",
|
|
28
|
+
"@testing-library/react": "^16.3.3",
|
|
29
|
+
"@types/react": "^19.3.0",
|
|
30
|
+
"@types/react-dom": "^19.3.0",
|
|
31
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
32
|
+
"jsdom": "^30.1.1",
|
|
33
|
+
"typescript": "~6.0.3",
|
|
34
|
+
"vitest": "~5.0.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
export default function Counter() {
|
|
6
|
+
const [count, setCount] = useState(0)
|
|
7
|
+
return (
|
|
8
|
+
<section className="counter" aria-label="Client interaction">
|
|
9
|
+
<div>
|
|
10
|
+
<p className="eyebrow">Client state</p>
|
|
11
|
+
<output aria-label="Count" aria-live="polite">{count}</output>
|
|
12
|
+
</div>
|
|
13
|
+
<div className="actions">
|
|
14
|
+
<button type="button" onClick={() => setCount(value => value + 1)}>Add one</button>
|
|
15
|
+
<button type="button" className="secondary" disabled={count === 0} onClick={() => setCount(0)}>Reset</button>
|
|
16
|
+
</div>
|
|
17
|
+
</section>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
@charset "UTF-8";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
5
|
+
font-synthesis: none;
|
|
6
|
+
color: #1b263b;
|
|
7
|
+
background: #f5f3ee;
|
|
8
|
+
text-rendering: optimizelegibility;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
* {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
margin: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
main {
|
|
20
|
+
width: min(100% - 3rem, 48rem);
|
|
21
|
+
margin: 12vh auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.eyebrow {
|
|
25
|
+
margin: 0 0 1.5rem;
|
|
26
|
+
font-size: 0.75rem;
|
|
27
|
+
font-weight: 700;
|
|
28
|
+
text-transform: uppercase;
|
|
29
|
+
letter-spacing: 0.12em;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
h1 {
|
|
33
|
+
margin: 0 0 1.5rem;
|
|
34
|
+
font-size: clamp(2.5rem, 7vw, 4.5rem);
|
|
35
|
+
line-height: 1.05;
|
|
36
|
+
letter-spacing: -0.05em;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.intro {
|
|
40
|
+
max-width: 35rem;
|
|
41
|
+
font-size: 1.125rem;
|
|
42
|
+
line-height: 1.7;
|
|
43
|
+
color: #536073;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.counter {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-wrap: wrap;
|
|
49
|
+
gap: 2rem;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
padding: 2rem 0;
|
|
53
|
+
margin: 3rem 0;
|
|
54
|
+
border-block: 1px solid #c7cbd1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.counter .eyebrow {
|
|
58
|
+
margin-bottom: 0.75rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
output {
|
|
62
|
+
font-size: 3rem;
|
|
63
|
+
font-variant-numeric: tabular-nums;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.actions {
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-wrap: wrap;
|
|
69
|
+
gap: 0.75rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
button {
|
|
73
|
+
min-height: 2.75rem;
|
|
74
|
+
padding: 0.75rem 1.125rem;
|
|
75
|
+
font: inherit;
|
|
76
|
+
color: #fff;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
background: #1b263b;
|
|
79
|
+
border: 1px solid #1b263b;
|
|
80
|
+
border-radius: 0.25rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.secondary {
|
|
84
|
+
color: #1b263b;
|
|
85
|
+
background: transparent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
button:disabled {
|
|
89
|
+
cursor: default;
|
|
90
|
+
opacity: 0.45;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
button:focus-visible,
|
|
94
|
+
a:focus-visible {
|
|
95
|
+
outline: 3px solid #b95732;
|
|
96
|
+
outline-offset: 4px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
footer {
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-wrap: wrap;
|
|
102
|
+
gap: 1rem 2rem;
|
|
103
|
+
font-size: 0.875rem;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
a {
|
|
107
|
+
color: inherit;
|
|
108
|
+
text-underline-offset: 0.25em;
|
|
109
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* eslint react-refresh/only-export-components: ["error", { "allowExportNames": ["metadata"] }] -- Next.js reads metadata from route layouts. */
|
|
2
|
+
import type { Metadata } from 'next'
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import './globals.css'
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: 'Next.js workspace',
|
|
8
|
+
description: 'A Next.js App Router application in your pnpm workspace.',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
12
|
+
return (
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<body>{children}</body>
|
|
15
|
+
</html>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Counter from './counter'
|
|
2
|
+
|
|
3
|
+
export default function HomePage() {
|
|
4
|
+
return (
|
|
5
|
+
<main>
|
|
6
|
+
<p className="eyebrow">repoctl / Next.js</p>
|
|
7
|
+
<h1>Next.js workspace</h1>
|
|
8
|
+
<p className="intro">A server-rendered page with a small client component. Ready for your next feature.</p>
|
|
9
|
+
<Counter />
|
|
10
|
+
<footer>
|
|
11
|
+
<a href="/api/health">Health endpoint</a>
|
|
12
|
+
<a href="https://nextjs.org/docs/app">App Router documentation</a>
|
|
13
|
+
</footer>
|
|
14
|
+
</main>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
2
|
+
import { afterEach, expect, it } from 'vitest'
|
|
3
|
+
import Counter from '../src/app/counter'
|
|
4
|
+
|
|
5
|
+
afterEach(cleanup)
|
|
6
|
+
|
|
7
|
+
it('increments the count and resets it to zero', () => {
|
|
8
|
+
render(<Counter />)
|
|
9
|
+
const count = screen.getByLabelText('Count')
|
|
10
|
+
const reset = screen.getByRole('button', { name: 'Reset' })
|
|
11
|
+
expect(count.textContent).toBe('0')
|
|
12
|
+
expect(reset.hasAttribute('disabled')).toBe(true)
|
|
13
|
+
|
|
14
|
+
fireEvent.click(screen.getByRole('button', { name: 'Add one' }))
|
|
15
|
+
fireEvent.click(screen.getByRole('button', { name: 'Add one' }))
|
|
16
|
+
expect(count.textContent).toBe('2')
|
|
17
|
+
expect(reset.hasAttribute('disabled')).toBe(false)
|
|
18
|
+
|
|
19
|
+
fireEvent.click(reset)
|
|
20
|
+
expect(count.textContent).toBe('0')
|
|
21
|
+
expect(reset.hasAttribute('disabled')).toBe(true)
|
|
22
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"incremental": true,
|
|
5
|
+
"tsBuildInfoFile": "./node_modules/.tmp/next.tsbuildinfo",
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"lib": [
|
|
9
|
+
"DOM",
|
|
10
|
+
"DOM.Iterable",
|
|
11
|
+
"ESNext"
|
|
12
|
+
],
|
|
13
|
+
"paths": {
|
|
14
|
+
"@/*": [
|
|
15
|
+
"./src/*"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"types": [
|
|
19
|
+
"node"
|
|
20
|
+
],
|
|
21
|
+
"isolatedModules": true,
|
|
22
|
+
"plugins": [
|
|
23
|
+
{
|
|
24
|
+
"name": "next"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"include": [
|
|
29
|
+
"next-env.d.ts",
|
|
30
|
+
"src/**/*.ts",
|
|
31
|
+
"src/**/*.tsx",
|
|
32
|
+
"test/**/*.tsx",
|
|
33
|
+
"next.config.ts",
|
|
34
|
+
".next/types/**/*.ts",
|
|
35
|
+
".next/dev/types/**/*.ts"
|
|
36
|
+
],
|
|
37
|
+
"exclude": [
|
|
38
|
+
"node_modules"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"//"
|
|
4
|
+
],
|
|
5
|
+
"tasks": {
|
|
6
|
+
"build": {
|
|
7
|
+
"inputs": [
|
|
8
|
+
"$TURBO_DEFAULT$",
|
|
9
|
+
".env*"
|
|
10
|
+
],
|
|
11
|
+
"outputs": [
|
|
12
|
+
".next/**",
|
|
13
|
+
"!.next/cache/**"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"typecheck": {
|
|
17
|
+
"cache": false
|
|
18
|
+
},
|
|
19
|
+
"start": {
|
|
20
|
+
"cache": false,
|
|
21
|
+
"persistent": true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import react from '@vitejs/plugin-react'
|
|
2
|
+
import { defineVitestProjectConfig } from 'repoctl/tooling'
|
|
3
|
+
import { mergeConfig } from 'vitest/config'
|
|
4
|
+
|
|
5
|
+
export default mergeConfig(await defineVitestProjectConfig({
|
|
6
|
+
options: { environment: 'jsdom' },
|
|
7
|
+
}), {
|
|
8
|
+
plugins: [react()],
|
|
9
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ice breaker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# React component library
|
|
2
|
+
|
|
3
|
+
An ESM-only React 19.3+ component library. `Counter` and `CounterProps` are exported
|
|
4
|
+
from the package root, and styles have an explicit `./style.css` export. React,
|
|
5
|
+
React DOM and their subpaths (including JSX runtimes) remain external to the bundle.
|
|
6
|
+
CommonJS is not a supported entry point.
|
|
7
|
+
The built JavaScript retains `use client`, so Next App Router Server Components
|
|
8
|
+
can import this stateful component directly. Callback props still belong in a
|
|
9
|
+
consumer Client Component.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import type { CounterProps } from 'your-package-name'
|
|
13
|
+
import { Counter } from 'your-package-name'
|
|
14
|
+
import 'your-package-name/style.css'
|
|
15
|
+
|
|
16
|
+
const props: CounterProps = { initialCount: 2, step: 2 }
|
|
17
|
+
export function Example() {
|
|
18
|
+
return <Counter {...props} onCountChange={console.log} />
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Install `react` and `react-dom` in the consuming application. Import the stylesheet
|
|
23
|
+
once in its application entry. `sideEffects` preserves CSS during tree shaking;
|
|
24
|
+
JavaScript does not automatically inject styles. `initialCount` initializes the
|
|
25
|
+
local state and supplies the reset value; this component is uncontrolled.
|
|
26
|
+
|
|
27
|
+
Run these commands from the workspace root, replacing the filter if you rename
|
|
28
|
+
the package. Build before running the tests: they import the built package entry.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm --filter @icebreakers/react-lib-template build
|
|
32
|
+
pnpm --filter @icebreakers/react-lib-template lint
|
|
33
|
+
pnpm --filter @icebreakers/react-lib-template typecheck
|
|
34
|
+
pnpm --filter @icebreakers/react-lib-template tsd
|
|
35
|
+
pnpm --filter @icebreakers/react-lib-template test
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The generated package starts with `private: true`. Before publishing, set your own
|
|
39
|
+
package name and version, remove `private` (or set it to `false`), review the MIT
|
|
40
|
+
license and run `repo package check` from the workspace root. Only `dist`, this
|
|
41
|
+
README, the license and the package manifest are packed. Validate a tarball in a
|
|
42
|
+
separate application before publishing. Storybook can be added independently;
|
|
43
|
+
it is not required to build or consume this library.
|
|
44
|
+
|
|
45
|
+
## 中文说明
|
|
46
|
+
|
|
47
|
+
此模板提供 React 19.3+ 的 ESM 组件库,默认目录为 `packages/react-lib`。
|
|
48
|
+
组件和 props 类型从包根入口导入,样式从 `包名/style.css` 显式导入。
|
|
49
|
+
React、React DOM 和 JSX runtime 不打进库;消费应用需要安装对应 peer 依赖。
|
|
50
|
+
默认保留 `private: true`,发布前请修改包名、版本和 private,完成上述构建、
|
|
51
|
+
ESLint/Stylelint、类型及行为验证,再执行 `repo package check` 和实际 tarball 消费验证。
|
|
52
|
+
本模板不声明 CommonJS 支持,Storybook 是可选扩展。
|