@icebreakers/monorepo-templates 0.1.10 → 0.1.12

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.
@@ -0,0 +1,51 @@
1
+ # Repository Guidelines
2
+
3
+ ## Project Structure & Module Organization
4
+
5
+ This pnpm + Turbo monorepo keeps template sources under `templates/` (`cli`, `client`, `server`, `vitepress`, `tsup`, `tsdown`, `unbuild`, `vue-lib`) and reusable tooling under `packages/` (e.g., `monorepo`, `create-icebreaker`). Shared TypeScript and build settings live in root configs such as `turbo.json`, `tsconfig.json`, and `eslint.config.js`. Tests sit alongside their targets in `test/*.test.ts`, and each app owns its public assets (`public/`, `worker/`) to keep deployments self-contained.
6
+
7
+ ## Build, Test, and Development Commands
8
+
9
+ - `pnpm install` — set up workspaces; ensure Node 20+ as defined in `package.json`.
10
+ - `pnpm dev` — run `turbo run dev --parallel` for all apps that expose a `dev` script.
11
+ - `pnpm build` — execute `turbo run build` to build every workspace with caching.
12
+ - `pnpm test` / `pnpm test:dev` — run Vitest suites once or in watch mode across packages.
13
+ - `pnpm lint` — invoke `turbo run lint` to apply ESLint/Stylelint policies repo-wide.
14
+ - `pnpm script:sync` & `pnpm script:clean` — use the monorepo helper to align dependency versions or clear generated artifacts.
15
+
16
+ ## Coding Style & Naming Conventions
17
+
18
+ Follow the root `.editorconfig`: two-space indentation, LF line endings, UTF-8. Prefer TypeScript (`.ts`/`.tsx`) and Vue SFCs; name files with kebab-case (`user-table.vue`) and exported symbols with PascalCase for components or camelCase for utilities. ESLint (`@icebreakers/eslint-config`) and Stylelint enforce formatting; run `pnpm lint` before committing, and rely on Husky + lint-staged to auto-fix staged files. Any style-related code changes (CSS, SCSS, Less, Vue style blocks, and similar files) must pass Stylelint checks before commit. All changed code must pass ESLint checks before commit.
19
+
20
+ `lint-staged.config.js` must include Stylelint checks for staged style files so style validation also runs during pre-commit.
21
+
22
+ When a single code file exceeds 300 lines, treat it as a refactor signal and split it into smaller modules.
23
+
24
+ When splitting code, do not create suffix-based sibling files such as `xxx.config.ts` or `xxx.filter.ts` just to move logic around. Create a dedicated folder (for example `xxx/`) and place the split modules inside it with clear responsibilities.
25
+
26
+ All generated code must pass ESLint checks, and all generated style files must pass Stylelint checks.
27
+ Generated Vue files or other SFC files that include style blocks are also required to pass Stylelint checks.
28
+
29
+ ## Testing Guidelines
30
+
31
+ Vitest powers unit tests located in `test/*.test.ts`. Mirror existing naming by matching the unit under test (`monorepo` utilities map to `packages/monorepo/test/*.test.ts`). Aim for meaningful assertions rather than snapshot defaults, and add coverage checks with `pnpm test -- --coverage`, which writes reports to `coverage/`. When introducing new public APIs, include integration-style tests in the relevant app workspace. AI-assisted validation must run a full test matrix when available, including unit, integration, and E2E tests.
32
+
33
+ Before running tests, build the packages first and run tests against build artifacts instead of source-only execution to better match real delivery behavior.
34
+
35
+ TypeScript issues must be fixed before commit. Do not commit with unresolved TypeScript compile or type-check errors.
36
+
37
+ TypeScript code must be validated with `tsc` (`pnpm typecheck` or the relevant workspace `typecheck` script) and must not contain type errors.
38
+
39
+ When building TypeScript libraries (type-focused APIs or public type definitions), add and maintain `tsd` type tests to verify exported type behavior.
40
+
41
+ ## Commit & Pull Request Guidelines
42
+
43
+ Commits must conform to Conventional Commit syntax; recent history uses prefixes like `feat`, `fix`, and `chore`. Example: `feat(server): add auth router`. Use `pnpm commit` (commitlint prompt) or ensure your manual message passes `pnpm commitlint --edit`. Before opening a PR, make sure `pnpm lint` and `pnpm test` succeed, link related issues, and provide screenshots or logs for user-facing changes. Touching publishable packages requires a changeset (`pnpm changeset`) so releases stay traceable.
44
+
45
+ For AI-generated or AI-assisted changes, use this verification order before commit or PR:
46
+
47
+ 1. Run build first (`pnpm build` or the relevant workspace build command).
48
+ 2. Run lint checks, ensuring all changed code passes ESLint and style-related code passes Stylelint.
49
+ 3. Run TypeScript checks and fix all type errors before proceeding.
50
+ 4. For TypeScript libraries, run `tsd` type tests and fix all failures before proceeding.
51
+ 5. Run tests against built artifacts, including unit, integration, and E2E suites (when available in the affected workspace).
package/assets/gitignore CHANGED
@@ -35,4 +35,5 @@ yarn-error.log*
35
35
  dist
36
36
  vite.config.ts.timestamp-*.mjs
37
37
  config.ts.timestamp-*.mjs
38
- .vite-inspect
38
+ .vite-inspect
39
+ .playwright-cli
@@ -3,7 +3,7 @@
3
3
  "type": "module",
4
4
  "version": "0.0.0",
5
5
  "private": true,
6
- "packageManager": "pnpm@10.29.3",
6
+ "packageManager": "pnpm@10.31.0",
7
7
  "author": "ice breaker <hi@sonofmagic.top>",
8
8
  "repository": {
9
9
  "type": "git",
@@ -45,36 +45,36 @@
45
45
  "script:mirror": "monorepo mirror"
46
46
  },
47
47
  "devDependencies": {
48
- "@changesets/cli": "^2.29.8",
49
- "@commitlint/cli": "^20.4.1",
50
- "@icebreakers/changelog-github": "^0.2.0",
51
- "@icebreakers/commitlint-config": "^1.2.7",
52
- "@icebreakers/eslint-config": "^1.6.19",
48
+ "@changesets/cli": "^2.30.0",
49
+ "@commitlint/cli": "^20.4.3",
50
+ "@icebreakers/changelog-github": "^0.2.1",
51
+ "@icebreakers/commitlint-config": "^1.2.10",
52
+ "@icebreakers/eslint-config": "^1.6.25",
53
53
  "@icebreakers/monorepo": "workspace:*",
54
- "@icebreakers/stylelint-config": "^2.0.2",
54
+ "@icebreakers/stylelint-config": "^2.0.3",
55
55
  "@types/fs-extra": "^11.0.4",
56
- "@types/node": "^25.2.3",
56
+ "@types/node": "^25.3.5",
57
57
  "@types/semver": "^7.7.1",
58
58
  "@vitest/coverage-v8": "~4.0.18",
59
59
  "ci-info": "^4.4.0",
60
60
  "cross-env": "^10.1.0",
61
61
  "defu": "^6.1.4",
62
- "es-toolkit": "^1.44.0",
63
- "eslint": "^10.0.0",
62
+ "es-toolkit": "^1.45.1",
63
+ "eslint": "^10.0.3",
64
64
  "execa": "^9.6.1",
65
- "fs-extra": "^11.3.3",
65
+ "fs-extra": "^11.3.4",
66
66
  "husky": "^9.1.7",
67
- "lint-staged": "^16.2.7",
67
+ "lint-staged": "^16.3.2",
68
68
  "only-allow": "^1.2.2",
69
69
  "pathe": "^2.0.3",
70
70
  "pkg-types": "^2.3.0",
71
- "rimraf": "^6.1.2",
72
- "stylelint": "^17.3.0",
73
- "tsdown": "0.20.3",
71
+ "rimraf": "^6.1.3",
72
+ "stylelint": "^17.4.0",
73
+ "tsdown": "0.21.1",
74
74
  "tslib": "^2.8.1",
75
75
  "tsup": "^8.5.1",
76
76
  "tsx": "^4.21.0",
77
- "turbo": "^2.8.8",
77
+ "turbo": "^2.8.14",
78
78
  "type-fest": "^5.4.4",
79
79
  "typescript": "^5.9.3",
80
80
  "unbuild": "^3.6.1",
package/assets-data.mjs CHANGED
@@ -12,6 +12,7 @@ const coreAssetTargets = [
12
12
  'stylelint.config.js',
13
13
  'monorepo.config.ts',
14
14
  'package.json',
15
+ 'AGENTS.md',
15
16
  // pnpm
16
17
  'pnpm-workspace.yaml',
17
18
  // base tsconfig
package/dist/index.d.mts CHANGED
@@ -29,6 +29,9 @@ interface PrepareAssetsOptions {
29
29
  }
30
30
  declare function prepareAssets(options?: PrepareAssetsOptions): Promise<void>;
31
31
  //#endregion
32
+ //#region src/runtime-assets.d.ts
33
+ declare function ensureTemplateAssetsPrepared(): Promise<void>;
34
+ //#endregion
32
35
  //#region src/scaffold.d.ts
33
36
  type TargetMode = 'prepare' | 'ensure' | 'skip';
34
37
  interface ScaffoldTemplateOptions {
@@ -74,4 +77,4 @@ declare const templateSourceMap: Record<string, string>;
74
77
  declare const templateTargetMap: Record<string, string>;
75
78
  declare const templateMap: Record<string, string>;
76
79
  //#endregion
77
- export { Command, type TemplateChoice, type TemplateDefinition, assetTargets, assetsDir, checkbox, createTemplateCopyFilter, execaCommand, getAssetTargets, input, isGitignoreFile, packageDir, prepareAssets, program, runCommand, scaffoldTemplate, scaffoldWorkspace, select, shouldSkipTemplatePath, skeletonDir, templateChoices, templateMap, templateSourceMap, templateTargetMap, templatesDir, toPublishGitignorePath, toWorkspaceGitignorePath };
80
+ export { Command, type TemplateChoice, type TemplateDefinition, assetTargets, assetsDir, checkbox, createTemplateCopyFilter, ensureTemplateAssetsPrepared, execaCommand, getAssetTargets, input, isGitignoreFile, packageDir, prepareAssets, program, runCommand, scaffoldTemplate, scaffoldWorkspace, select, shouldSkipTemplatePath, skeletonDir, templateChoices, templateMap, templateSourceMap, templateTargetMap, templatesDir, toPublishGitignorePath, toWorkspaceGitignorePath };
package/dist/index.mjs CHANGED
@@ -1,13 +1,12 @@
1
1
  import * as path$1 from "node:path";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import fs from "node:fs/promises";
4
+ import fs, { access, open, rm } from "node:fs/promises";
5
5
  import { execa, execaCommand } from "execa";
6
6
  import checkbox from "@inquirer/checkbox";
7
7
  import input from "@inquirer/input";
8
8
  import select from "@inquirer/select";
9
9
  import { Command, program } from "commander";
10
-
11
10
  //#region assets-data.mjs
12
11
  const coreAssetTargets = [
13
12
  ".changeset",
@@ -23,6 +22,7 @@ const coreAssetTargets = [
23
22
  "stylelint.config.js",
24
23
  "monorepo.config.ts",
25
24
  "package.json",
25
+ "AGENTS.md",
26
26
  "pnpm-workspace.yaml",
27
27
  "tsconfig.json",
28
28
  "turbo.json",
@@ -43,7 +43,6 @@ const assetTargets$1 = [...coreAssetTargets, ...extraAssetTargets];
43
43
  function getAssetTargets$1(core = false) {
44
44
  return core ? [...coreAssetTargets] : [...assetTargets$1];
45
45
  }
46
-
47
46
  //#endregion
48
47
  //#region template-data.mjs
49
48
  const templateChoices$1 = [
@@ -96,18 +95,17 @@ const templateChoices$1 = [
96
95
  target: "apps/cli"
97
96
  }
98
97
  ];
99
-
100
98
  //#endregion
101
99
  //#region src/paths.ts
102
100
  const packageDir = path$1.resolve(fileURLToPath(new URL("../", import.meta.url)));
103
101
  const templatesDir = path$1.join(packageDir, "templates");
104
102
  const assetsDir = path$1.join(packageDir, "assets");
105
103
  const skeletonDir = assetsDir;
106
-
107
104
  //#endregion
108
105
  //#region src/utils/gitignore.ts
109
106
  const publishBasename = "gitignore";
110
107
  const workspaceBasename = ".gitignore";
108
+ const normalizedPathPattern = /[\\/]/;
111
109
  function detectSeparator(input) {
112
110
  if (input.includes("\\") && !input.includes("/")) return "\\";
113
111
  return "/";
@@ -115,10 +113,10 @@ function detectSeparator(input) {
115
113
  function replaceBasename(input, from, to) {
116
114
  if (!input) return input;
117
115
  const separator = detectSeparator(input);
118
- const normalized = input.replace(/[\\/]/g, separator);
116
+ const normalized = input.replace(normalizedPathPattern, separator);
119
117
  const hasTrailingSeparator = normalized.endsWith(separator);
120
118
  const segments = normalized.split(separator);
121
- if (hasTrailingSeparator && segments[segments.length - 1] === "") segments.pop();
119
+ if (hasTrailingSeparator && segments.at(-1) === "") segments.pop();
122
120
  const lastIndex = segments.length - 1;
123
121
  if (lastIndex >= 0 && segments[lastIndex] === from) {
124
122
  segments[lastIndex] = to;
@@ -136,7 +134,6 @@ function toWorkspaceGitignorePath(input) {
136
134
  function isGitignoreFile(name) {
137
135
  return toPublishGitignorePath(name) !== name || toWorkspaceGitignorePath(name) !== name;
138
136
  }
139
-
140
137
  //#endregion
141
138
  //#region src/utils/template-filter.ts
142
139
  const skipDirs = new Set([
@@ -169,10 +166,10 @@ function shouldSkipTemplatePath(rootDir, targetPath) {
169
166
  function createTemplateCopyFilter(rootDir) {
170
167
  return (src) => !shouldSkipTemplatePath(rootDir, src);
171
168
  }
172
-
173
169
  //#endregion
174
170
  //#region src/prepare.ts
175
- async function pathExists(targetPath) {
171
+ const huskySkippedEntryPattern = /[\\/]_$/;
172
+ async function pathExists$1(targetPath) {
176
173
  try {
177
174
  await fs.access(targetPath);
178
175
  return true;
@@ -199,7 +196,7 @@ async function renameGitignoreFiles(targetDir) {
199
196
  }));
200
197
  }
201
198
  async function resetDir(targetDir, overwriteExisting) {
202
- if (!overwriteExisting && await pathExists(targetDir)) return;
199
+ if (!overwriteExisting && await pathExists$1(targetDir)) return;
203
200
  try {
204
201
  await fs.rm(targetDir, {
205
202
  recursive: true,
@@ -212,7 +209,7 @@ async function resetDir(targetDir, overwriteExisting) {
212
209
  await fs.mkdir(targetDir, { recursive: true });
213
210
  }
214
211
  async function copyEntry(from, to, overwriteExisting, filter) {
215
- if (!overwriteExisting && await pathExists(to)) return;
212
+ if (!overwriteExisting && await pathExists$1(to)) return;
216
213
  try {
217
214
  await fs.cp(from, to, {
218
215
  recursive: true,
@@ -228,17 +225,17 @@ async function copyEntry(from, to, overwriteExisting, filter) {
228
225
  async function copyAssets(repoRoot, overwriteExisting) {
229
226
  for (const target of assetTargets$1) {
230
227
  const from = path$1.join(repoRoot, target);
231
- if (!await pathExists(from)) continue;
228
+ if (!await pathExists$1(from)) continue;
232
229
  const to = path$1.join(assetsDir, toPublishGitignorePath(target));
233
230
  const stats = await fs.stat(from);
234
- await copyEntry(from, to, overwriteExisting, target === ".husky" ? (src) => !/[\\/]_$/.test(src) : void 0);
231
+ await copyEntry(from, to, overwriteExisting, target === ".husky" ? (src) => !huskySkippedEntryPattern.test(src) : void 0);
235
232
  if (stats.isDirectory()) await renameGitignoreFiles(to);
236
233
  }
237
234
  }
238
235
  async function copyTemplates(repoRoot, overwriteExisting) {
239
236
  for (const template of templateChoices$1) {
240
237
  const from = path$1.join(repoRoot, "templates", template.source);
241
- if (!await pathExists(from)) continue;
238
+ if (!await pathExists$1(from)) continue;
242
239
  const to = path$1.join(templatesDir, template.source);
243
240
  const filter = (src) => !shouldSkipTemplatePath(from, src);
244
241
  await copyEntry(from, to, overwriteExisting, filter);
@@ -248,13 +245,75 @@ async function copyTemplates(repoRoot, overwriteExisting) {
248
245
  async function prepareAssets(options = {}) {
249
246
  const overwriteExisting = options.overwriteExisting ?? true;
250
247
  const repoRoot = path$1.resolve(packageDir, "..", "..");
251
- if (!await pathExists(path$1.join(repoRoot, "templates"))) return;
248
+ if (!await pathExists$1(path$1.join(repoRoot, "templates"))) return;
252
249
  await resetDir(assetsDir, overwriteExisting);
253
250
  await resetDir(templatesDir, overwriteExisting);
254
251
  await copyAssets(repoRoot, overwriteExisting);
255
252
  await copyTemplates(repoRoot, overwriteExisting);
256
253
  }
257
-
254
+ //#endregion
255
+ //#region src/runtime-assets.ts
256
+ const lockFileName = ".prepare-assets.lock";
257
+ const lockPollIntervalMs = 200;
258
+ const lockTimeoutMs = 3e4;
259
+ let ensurePromise = null;
260
+ async function pathExists(targetPath) {
261
+ try {
262
+ await access(targetPath);
263
+ return true;
264
+ } catch {
265
+ return false;
266
+ }
267
+ }
268
+ async function acquireLock(lockPath) {
269
+ try {
270
+ return await open(lockPath, "wx");
271
+ } catch (error) {
272
+ if (error.code === "EEXIST") return null;
273
+ throw error;
274
+ }
275
+ }
276
+ async function isPrepared() {
277
+ const checks = [
278
+ path.join(assetsDir, "AGENTS.md"),
279
+ path.join(assetsDir, "LICENSE"),
280
+ path.join(templatesDir, "unbuild")
281
+ ];
282
+ return (await Promise.all(checks.map(pathExists))).every(Boolean);
283
+ }
284
+ async function waitForPrepared() {
285
+ const deadline = Date.now() + lockTimeoutMs;
286
+ while (Date.now() < deadline) {
287
+ if (await isPrepared()) return true;
288
+ await new Promise((resolve) => setTimeout(resolve, lockPollIntervalMs));
289
+ }
290
+ return false;
291
+ }
292
+ async function runEnsure() {
293
+ if (await isPrepared()) return;
294
+ const lockPath = path.join(packageDir, lockFileName);
295
+ let lockHandle = await acquireLock(lockPath);
296
+ if (!lockHandle) {
297
+ if (await waitForPrepared()) return;
298
+ lockHandle = await acquireLock(lockPath);
299
+ if (!lockHandle) throw new Error(`Failed to prepare template assets: lock is held at ${lockPath}`);
300
+ }
301
+ try {
302
+ await prepareAssets({
303
+ overwriteExisting: false,
304
+ silent: true
305
+ });
306
+ } finally {
307
+ await lockHandle.close().catch(() => {});
308
+ await rm(lockPath, { force: true }).catch(() => {});
309
+ }
310
+ }
311
+ async function ensureTemplateAssetsPrepared() {
312
+ if (!ensurePromise) ensurePromise = runEnsure().finally(() => {
313
+ ensurePromise = null;
314
+ });
315
+ await ensurePromise;
316
+ }
258
317
  //#endregion
259
318
  //#region src/scaffold.ts
260
319
  async function isEmptyDir(dir) {
@@ -291,12 +350,13 @@ async function copyDirContents(sourceDir, targetDir, options = {}) {
291
350
  const targetName = renameEntry(entry.name);
292
351
  const to = path.join(targetDir, targetName);
293
352
  if (entry.isDirectory()) {
294
- await copyDirContents(from, to, {
353
+ const nestedOptions = {
295
354
  rootDir,
296
355
  filter,
297
- renameEntry,
298
- skipRootBasenames: options.skipRootBasenames
299
- });
356
+ renameEntry
357
+ };
358
+ if (options.skipRootBasenames) nestedOptions.skipRootBasenames = options.skipRootBasenames;
359
+ await copyDirContents(from, to, { ...nestedOptions });
300
360
  continue;
301
361
  }
302
362
  if (entry.isSymbolicLink()) {
@@ -307,18 +367,24 @@ async function copyDirContents(sourceDir, targetDir, options = {}) {
307
367
  await fs.cp(from, to);
308
368
  }
309
369
  }
370
+ function isDefaultTemplatesPath(sourceDir) {
371
+ const resolvedSource = path.resolve(sourceDir);
372
+ const resolvedTemplatesRoot = path.resolve(templatesDir);
373
+ return resolvedSource === resolvedTemplatesRoot || resolvedSource.startsWith(`${resolvedTemplatesRoot}${path.sep}`);
374
+ }
310
375
  async function scaffoldTemplate(options) {
311
376
  const { sourceDir, targetDir, ensureTargetDir = true, filter, renameEntry, skipRootBasenames } = options;
377
+ if (isDefaultTemplatesPath(sourceDir)) await ensureTemplateAssetsPrepared();
312
378
  if (ensureTargetDir) await fs.mkdir(targetDir, { recursive: true });
313
- await copyDirContents(sourceDir, targetDir, {
314
- rootDir: sourceDir,
315
- filter,
316
- renameEntry,
317
- skipRootBasenames
318
- });
379
+ const copyOptions = { rootDir: sourceDir };
380
+ if (filter) copyOptions.filter = filter;
381
+ if (renameEntry) copyOptions.renameEntry = renameEntry;
382
+ if (skipRootBasenames) copyOptions.skipRootBasenames = skipRootBasenames;
383
+ await copyDirContents(sourceDir, targetDir, copyOptions);
319
384
  }
320
385
  async function scaffoldWorkspace(options) {
321
386
  const { targetDir, templateKeys = [], assetsDir: assetsDir$1 = assetsDir, templatesDir: templatesDir$1 = templatesDir, includeAssets = true, targetMode = "prepare", force = false } = options;
387
+ if (includeAssets && path.resolve(assetsDir$1) === path.resolve(assetsDir) || templateKeys.length > 0 && path.resolve(templatesDir$1) === path.resolve(templatesDir)) await ensureTemplateAssetsPrepared();
322
388
  if (targetMode === "prepare") await prepareTargetDir(targetDir, { force });
323
389
  else if (targetMode === "ensure") await fs.mkdir(targetDir, { recursive: true });
324
390
  if (includeAssets) await copyDirContents(assetsDir$1, targetDir, { rootDir: assetsDir$1 });
@@ -332,7 +398,6 @@ async function scaffoldWorkspace(options) {
332
398
  });
333
399
  }
334
400
  }
335
-
336
401
  //#endregion
337
402
  //#region src/utils/command.ts
338
403
  async function runCommand(command, args, options = {}) {
@@ -341,7 +406,6 @@ async function runCommand(command, args, options = {}) {
341
406
  ...options
342
407
  });
343
408
  }
344
-
345
409
  //#endregion
346
410
  //#region src/index.ts
347
411
  const templateChoices = templateChoices$1;
@@ -350,6 +414,5 @@ const getAssetTargets = getAssetTargets$1;
350
414
  const templateSourceMap = Object.fromEntries(templateChoices.map((item) => [item.key, item.source]));
351
415
  const templateTargetMap = Object.fromEntries(templateChoices.map((item) => [item.key, item.target]));
352
416
  const templateMap = templateSourceMap;
353
-
354
417
  //#endregion
355
- export { Command, assetTargets, assetsDir, checkbox, createTemplateCopyFilter, execaCommand, getAssetTargets, input, isGitignoreFile, packageDir, prepareAssets, program, runCommand, scaffoldTemplate, scaffoldWorkspace, select, shouldSkipTemplatePath, skeletonDir, templateChoices, templateMap, templateSourceMap, templateTargetMap, templatesDir, toPublishGitignorePath, toWorkspaceGitignorePath };
418
+ export { Command, assetTargets, assetsDir, checkbox, createTemplateCopyFilter, ensureTemplateAssetsPrepared, execaCommand, getAssetTargets, input, isGitignoreFile, packageDir, prepareAssets, program, runCommand, scaffoldTemplate, scaffoldWorkspace, select, shouldSkipTemplatePath, skeletonDir, templateChoices, templateMap, templateSourceMap, templateTargetMap, templatesDir, toPublishGitignorePath, toWorkspaceGitignorePath };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@icebreakers/monorepo-templates",
3
3
  "type": "module",
4
- "version": "0.1.10",
4
+ "version": "0.1.12",
5
5
  "description": "Template assets for icebreaker monorepo scaffolding",
6
6
  "author": "ice breaker <hi@sonofmagic.top>",
7
7
  "license": "MIT",
@@ -39,9 +39,9 @@
39
39
  "node": ">=18.0.0"
40
40
  },
41
41
  "dependencies": {
42
- "@inquirer/checkbox": "^5.0.4",
43
- "@inquirer/input": "^5.0.4",
44
- "@inquirer/select": "^5.0.4",
42
+ "@inquirer/checkbox": "^5.1.0",
43
+ "@inquirer/input": "^5.0.8",
44
+ "@inquirer/select": "^5.1.0",
45
45
  "commander": "^14.0.3",
46
46
  "execa": "^9.6.1"
47
47
  },
@@ -22,7 +22,8 @@
22
22
  "dev": "tsup --watch --sourcemap",
23
23
  "build": "tsup",
24
24
  "lint": "eslint .",
25
- "lint:fix": "eslint . --fix"
25
+ "lint:fix": "eslint . --fix",
26
+ "typecheck": "tsc -p tsconfig.json"
26
27
  },
27
28
  "dependencies": {
28
29
  "consola": "^3.4.2"
@@ -0,0 +1,28 @@
1
+ # Template Guidelines (`templates/client`)
2
+
3
+ This file adds template-local guidance. Global rules are defined in the repository root `AGENTS.md` and still apply.
4
+
5
+ ## Scope
6
+
7
+ - Template path: `templates/client`
8
+ - Purpose: Vue + Vite + Cloudflare Worker client template
9
+
10
+ ## Structure
11
+
12
+ - `src/`: app UI, pages, router, stores, trpc client
13
+ - `worker/`: worker/server entry and router definitions
14
+ - `public/`: static assets
15
+ - `tsconfig*.json`: split TS projects (`app`, `node`, `worker`)
16
+
17
+ ## Local Rules
18
+
19
+ - Keep browser code in `src/` and worker runtime code in `worker/`; avoid cross-layer coupling.
20
+ - Keep `typecheck` aligned with `vue-tsc -b` and project references in `tsconfig.json`.
21
+ - Tailwind ESLint config uses an absolute entry path in `eslint.config.js`; keep that pattern to avoid lint-staged path resolution issues.
22
+ - Prefer small feature folders inside `src/` (for example `src/features/<name>/`) when modules grow.
23
+
24
+ ## Validation
25
+
26
+ - `pnpm --filter @icebreakers/client lint`
27
+ - `pnpm --filter @icebreakers/client typecheck`
28
+ - `pnpm --filter @icebreakers/client build`
@@ -1,11 +1,14 @@
1
+ import { fileURLToPath } from 'node:url'
1
2
  import { icebreaker } from '@icebreakers/eslint-config'
2
3
 
4
+ const tailwindEntryPoint = fileURLToPath(new URL('./src/style.css', import.meta.url))
5
+
3
6
  export default icebreaker(
4
7
  {
5
8
  vue: true,
6
9
  typescript: true,
7
10
  tailwindcss: {
8
- entryPoint: './src/style.css',
11
+ entryPoint: tailwindEntryPoint,
9
12
  },
10
13
  ignores: ['**/fixtures/**'],
11
14
  },
@@ -11,42 +11,43 @@
11
11
  "preview": "vite preview",
12
12
  "deploy": "wrangler deploy",
13
13
  "cf-typegen": "wrangler types",
14
- "postinstall": "pnpm cf-typegen"
14
+ "postinstall": "pnpm cf-typegen",
15
+ "typecheck": "vue-tsc -b"
15
16
  },
16
17
  "dependencies": {
17
18
  "@faker-js/faker": "^10.3.0",
18
19
  "@tanstack/vue-query": "^5.92.9",
19
20
  "@tanstack/vue-query-devtools": "^6.1.5",
20
21
  "@tanstack/vue-table": "^8.21.3",
21
- "@tanstack/vue-virtual": "^3.13.18",
22
- "@trpc/client": "^11.10.0",
22
+ "@tanstack/vue-virtual": "^3.13.21",
23
+ "@trpc/client": "^11.12.0",
23
24
  "clsx": "^2.1.1",
24
25
  "pinia": "^3.0.4",
25
- "tailwind-merge": "^3.4.0",
26
+ "tailwind-merge": "^3.5.0",
26
27
  "tailwind-variants": "^3.2.2",
27
- "vue": "^3.5.28",
28
- "vue-i18n": "^11.2.8",
29
- "vue-router": "^5.0.2"
28
+ "vue": "^3.5.29",
29
+ "vue-i18n": "^11.3.0",
30
+ "vue-router": "^5.0.3"
30
31
  },
31
32
  "devDependencies": {
32
- "@cloudflare/vite-plugin": "^1.25.0",
33
- "@hono/node-server": "^1.19.9",
33
+ "@cloudflare/vite-plugin": "^1.26.1",
34
+ "@hono/node-server": "^1.19.11",
34
35
  "@hono/trpc-server": "^0.4.2",
35
- "@tailwindcss/vite": "^4.1.18",
36
- "@trpc/server": "^11.10.0",
36
+ "@tailwindcss/vite": "^4.2.1",
37
+ "@trpc/server": "^11.12.0",
37
38
  "@vitejs/plugin-vue": "^6.0.4",
38
39
  "@vitejs/plugin-vue-jsx": "^5.1.4",
39
- "@vue/tsconfig": "^0.8.1",
40
+ "@vue/tsconfig": "^0.9.0",
40
41
  "consola": "^3.4.2",
41
- "hono": "^4.11.9",
42
- "tailwindcss": "^4.1.18",
42
+ "hono": "^4.12.5",
43
+ "tailwindcss": "^4.2.1",
43
44
  "typescript": "~5.9.3",
44
45
  "unplugin-vue-router": "^0.19.2",
45
46
  "vite": "^7.3.1",
46
- "vite-plugin-vue-devtools": "^8.0.6",
47
+ "vite-plugin-vue-devtools": "^8.0.7",
47
48
  "vite-tsconfig-paths": "^6.1.1",
48
- "vue-tsc": "3.2.4",
49
- "wrangler": "^4.65.0",
49
+ "vue-tsc": "3.2.5",
50
+ "wrangler": "^4.71.0",
50
51
  "zod": "^4.3.6"
51
52
  }
52
53
  }
@@ -36,7 +36,8 @@
36
36
  "lint:fix": "eslint . --fix",
37
37
  "deploy": "wrangler deploy",
38
38
  "cf-typegen": "wrangler types",
39
- "postinstall": "pnpm cf-typegen"
39
+ "postinstall": "pnpm cf-typegen",
40
+ "typecheck": "tsc -p tsconfig.json"
40
41
  },
41
42
  "publishConfig": {
42
43
  "exports": {
@@ -51,10 +52,10 @@
51
52
  "types": "./dist/index.d.ts"
52
53
  },
53
54
  "devDependencies": {
54
- "@hono/node-server": "^1.19.9",
55
+ "@hono/node-server": "^1.19.11",
55
56
  "consola": "^3.4.2",
56
- "hono": "^4.11.9",
57
- "wrangler": "^4.65.0",
57
+ "hono": "^4.12.5",
58
+ "wrangler": "^4.71.0",
58
59
  "zod": "^4.3.6"
59
60
  }
60
61
  }
@@ -41,7 +41,8 @@
41
41
  "test:dev": "vitest",
42
42
  "release": "pnpm publish",
43
43
  "lint": "eslint .",
44
- "lint:fix": "eslint . --fix"
44
+ "lint:fix": "eslint . --fix",
45
+ "typecheck": "tsc -p tsconfig.json"
45
46
  },
46
47
  "publishConfig": {}
47
48
  }
@@ -42,7 +42,8 @@
42
42
  "test:dev": "vitest",
43
43
  "release": "pnpm publish",
44
44
  "lint": "eslint .",
45
- "lint:fix": "eslint . --fix"
45
+ "lint:fix": "eslint . --fix",
46
+ "typecheck": "tsc -p tsconfig.json"
46
47
  },
47
48
  "publishConfig": {}
48
49
  }
@@ -42,7 +42,8 @@
42
42
  "test:dev": "vitest",
43
43
  "release": "pnpm publish",
44
44
  "lint": "eslint .",
45
- "lint:fix": "eslint . --fix"
45
+ "lint:fix": "eslint . --fix",
46
+ "typecheck": "tsc -p tsconfig.json"
46
47
  },
47
48
  "publishConfig": {}
48
49
  }
@@ -1,3 +1,5 @@
1
+ const lowercaseLetterPattern = /[a-z]/g
2
+
1
3
  export function format(str: string) {
2
- return str.replaceAll(/[a-z]/g, s => s.toUpperCase())
4
+ return str.replaceAll(lowercaseLetterPattern, s => s.toUpperCase())
3
5
  }
@@ -19,20 +19,21 @@
19
19
  "build": "vitepress build",
20
20
  "preview": "vitepress preview",
21
21
  "lint": "eslint .",
22
- "lint:fix": "eslint . --fix"
22
+ "lint:fix": "eslint . --fix",
23
+ "typecheck": "tsc -p tsconfig.json"
23
24
  },
24
25
  "dependencies": {
25
- "vue": "^3.5.28"
26
+ "vue": "^3.5.29"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@braintree/sanitize-url": "^7.1.2",
29
- "@tailwindcss/vite": "^4.1.18",
30
+ "@tailwindcss/vite": "^4.2.1",
30
31
  "cytoscape": "^3.33.1",
31
32
  "cytoscape-cose-bilkent": "^4.1.0",
32
33
  "dayjs": "^1.11.19",
33
34
  "debug": "^4.4.3",
34
- "mermaid": "^11.12.2",
35
- "tailwindcss": "^4.1.18",
35
+ "mermaid": "^11.12.3",
36
+ "tailwindcss": "^4.2.1",
36
37
  "vitepress": "^1.6.4",
37
38
  "vitepress-plugin-mermaid": "^2.0.17"
38
39
  }
@@ -49,7 +49,7 @@ npx create-turbo@latest
49
49
 
50
50
  这会生成一个 monorepo 结构,比如:
51
51
 
52
- ```
52
+ ```txt
53
53
  .
54
54
  ├── apps
55
55
  │ ├── web # 前端应用
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "resolveJsonModule": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": [
12
+ "why/examples/2.npm-basic-package/src/**/*.ts",
13
+ "why/examples/3.npm-basic-package/src/**/*.ts"
14
+ ]
15
+ }
@@ -14,7 +14,7 @@
14
14
  | `.mts` | TypeScript,**指定生成 ESM** | TS 编译器/工具链 | 同上;不由 Node 直接执行。编译后产出 `.mjs/.js`(ESM 语义),并可生成 `.d.mts`。 |
15
15
  | `.d.ts` | **类型声明文件**(仅类型,无运行时) | TypeScript 编译器、编辑器/语言服务、打包器的类型阶段 | **永不执行**。仅在类型检查/智能提示/发布类型时被“读取”。若包的 `package.json` 有 `"types": "index.d.ts"`(或在 `exports` 的 `types`/ `typesVersions` 配置),TS 会用它做类型来源。也存在 `.d.mts` / `.d.cts` 变体以对应 ESM/CJS。 |
16
16
 
17
- # 关键规则与细节
17
+ ## 关键规则与细节
18
18
 
19
19
  1. **Node.js 如何决定 CJS vs ESM(针对 `.js`)**
20
20
  - 在某个包目录(有 `package.json`)下:
@@ -10,13 +10,13 @@
10
10
 
11
11
  下面这 2 个标志想必大家在 `npmjs` 进行搜索的时候都见到过。
12
12
 
13
- <img width="40" src="../assets/npm-ts.svg" />
13
+ <img width="40" src="../assets/npm-ts.svg" alt="TypeScript built-in types badge" />
14
14
 
15
15
  这个标志代表这个包发布的时候,本身包里面就自己提供了 `dts`,这个需要在打包生成出 `dts` 之后,在 `package.json` 中,通过 `types` 字段指定了 `dts` 的位置
16
16
 
17
17
  比如我发的所有包 [weapp-tailwindcss](https://www.npmjs.com/package/weapp-tailwindcss), [weapp-vite](https://www.npmjs.com/package/weapp-vite), [modern-ahocorasick](https://www.npmjs.com/package/modern-ahocorasick) 等等...
18
18
 
19
- <img width="40" src="../assets/npm-dt.svg" />
19
+ <img width="40" src="../assets/npm-dt.svg" alt="DefinitelyTyped badge" />
20
20
 
21
21
  这个标志代表这个包发布的时候,本身包里面没有 `dts`,但是有好心人给他写了 `dts`,并发布到了 `@types/*` 这个作用域下了,所以这个包等同于也有 `dts`,而且 `vscode` 也在发现你安装了这样的包之后,也会去下载 `@types/*` 这样的包,当然更好的方式是你要显式安装 `@types` 包
22
22
 
@@ -29,9 +29,9 @@
29
29
  <div class="flex justify-center">
30
30
  <div class="flex items-center border p-6">
31
31
 
32
- <img class="w-12" src="../assets/npm-ts.svg" />
32
+ <img class="w-12" src="../assets/npm-ts.svg" alt="TypeScript built-in types badge" />
33
33
  <div class="text-5xl mx-5">></div>
34
- <img class="w-12" src="../assets/npm-dt.svg" />
34
+ <img class="w-12" src="../assets/npm-dt.svg" alt="DefinitelyTyped badge" />
35
35
  <div class="text-5xl mx-5">></div>
36
36
  <div>什么标志都没有的包</div>
37
37
  </div>
@@ -88,6 +88,6 @@ declare function add(a: number, b: number): number
88
88
  - 手动编写 `.d.ts`
89
89
  - 自动生成(例如通过 `tsc --declaration`)
90
90
  - 安装类型定义包:
91
- ```
91
+ ```bash
92
92
  npm install --save-dev @types/express
93
93
  ```
@@ -6,7 +6,7 @@ outline: [2, 4]
6
6
 
7
7
  在 `Node.js` 中,**npm 包(npm package)** 是指一个通过 [npm(Node Package Manager)](https://www.npmjs.com/) 发布和管理的可重用 `JavaScript` 模块。它可以是一个库、工具、框架、命令行工具,甚至是一个完整的应用程序。
8
8
 
9
- ### 什么是 npm?
9
+ ## 什么是 npm?
10
10
 
11
11
  **npm(Node Package Manager)** 是 Node.js 的官方包管理工具,用于:
12
12
 
@@ -28,11 +28,11 @@ outline: [2, 4]
28
28
 
29
29
  ---
30
30
 
31
- ### 什么是一个 npm 包?
31
+ ## 什么是一个 npm 包?
32
32
 
33
33
  一个 **npm 包** 通常具有以下结构:
34
34
 
35
- ```
35
+ ```txt
36
36
  my-package/
37
37
  ├── package.json // 包的元数据(名字、版本、依赖等)
38
38
  ├── index.js // 主文件(或其它入口)
@@ -27,7 +27,8 @@
27
27
  "test:dev": "vitest",
28
28
  "release": "pnpm publish",
29
29
  "lint": "eslint .",
30
- "lint:fix": "eslint . --fix"
30
+ "lint:fix": "eslint . --fix",
31
+ "typecheck": "vue-tsc -b"
31
32
  },
32
33
  "publishConfig": {
33
34
  "exports": {
@@ -45,11 +46,11 @@
45
46
  "devDependencies": {
46
47
  "@vitejs/plugin-vue": "^6.0.4",
47
48
  "@vue/test-utils": "^2.4.6",
48
- "@vue/tsconfig": "^0.8.1",
49
- "jsdom": "^28.0.0",
49
+ "@vue/tsconfig": "^0.9.0",
50
+ "jsdom": "^28.1.0",
50
51
  "vite": "^7.3.1",
51
52
  "vite-plugin-dts": "^4.5.4",
52
- "vue": "^3.5.28",
53
- "vue-tsc": "^3.2.4"
53
+ "vue": "^3.5.29",
54
+ "vue-tsc": "^3.2.5"
54
55
  }
55
56
  }