@icebreakers/monorepo-templates 0.1.10 → 0.1.11

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,50 @@
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
+
28
+ ## Testing Guidelines
29
+
30
+ 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.
31
+
32
+ Before running tests, build the packages first and run tests against build artifacts instead of source-only execution to better match real delivery behavior.
33
+
34
+ TypeScript issues must be fixed before commit. Do not commit with unresolved TypeScript compile or type-check errors.
35
+
36
+ TypeScript code must be validated with `tsc` (`pnpm typecheck` or the relevant workspace `typecheck` script) and must not contain type errors.
37
+
38
+ When building TypeScript libraries (type-focused APIs or public type definitions), add and maintain `tsd` type tests to verify exported type behavior.
39
+
40
+ ## Commit & Pull Request Guidelines
41
+
42
+ 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.
43
+
44
+ For AI-generated or AI-assisted changes, use this verification order before commit or PR:
45
+
46
+ 1. Run build first (`pnpm build` or the relevant workspace build command).
47
+ 2. Run lint checks, ensuring all changed code passes ESLint and style-related code passes Stylelint.
48
+ 3. Run TypeScript checks and fix all type errors before proceeding.
49
+ 4. For TypeScript libraries, run `tsd` type tests and fix all failures before proceeding.
50
+ 5. Run tests against built artifacts, including unit, integration, and E2E suites (when available in the affected workspace).
@@ -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.30.2",
7
7
  "author": "ice breaker <hi@sonofmagic.top>",
8
8
  "repository": {
9
9
  "type": "git",
@@ -46,21 +46,21 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@changesets/cli": "^2.29.8",
49
- "@commitlint/cli": "^20.4.1",
49
+ "@commitlint/cli": "^20.4.2",
50
50
  "@icebreakers/changelog-github": "^0.2.0",
51
- "@icebreakers/commitlint-config": "^1.2.7",
52
- "@icebreakers/eslint-config": "^1.6.19",
51
+ "@icebreakers/commitlint-config": "^1.2.8",
52
+ "@icebreakers/eslint-config": "^1.6.21",
53
53
  "@icebreakers/monorepo": "workspace:*",
54
54
  "@icebreakers/stylelint-config": "^2.0.2",
55
55
  "@types/fs-extra": "^11.0.4",
56
- "@types/node": "^25.2.3",
56
+ "@types/node": "^25.3.0",
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
62
  "es-toolkit": "^1.44.0",
63
- "eslint": "^10.0.0",
63
+ "eslint": "^10.0.2",
64
64
  "execa": "^9.6.1",
65
65
  "fs-extra": "^11.3.3",
66
66
  "husky": "^9.1.7",
@@ -68,13 +68,13 @@
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",
71
+ "rimraf": "^6.1.3",
72
72
  "stylelint": "^17.3.0",
73
73
  "tsdown": "0.20.3",
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.10",
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,7 +1,7 @@
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";
@@ -23,6 +23,7 @@ const coreAssetTargets = [
23
23
  "stylelint.config.js",
24
24
  "monorepo.config.ts",
25
25
  "package.json",
26
+ "AGENTS.md",
26
27
  "pnpm-workspace.yaml",
27
28
  "tsconfig.json",
28
29
  "turbo.json",
@@ -172,7 +173,7 @@ function createTemplateCopyFilter(rootDir) {
172
173
 
173
174
  //#endregion
174
175
  //#region src/prepare.ts
175
- async function pathExists(targetPath) {
176
+ async function pathExists$1(targetPath) {
176
177
  try {
177
178
  await fs.access(targetPath);
178
179
  return true;
@@ -199,7 +200,7 @@ async function renameGitignoreFiles(targetDir) {
199
200
  }));
200
201
  }
201
202
  async function resetDir(targetDir, overwriteExisting) {
202
- if (!overwriteExisting && await pathExists(targetDir)) return;
203
+ if (!overwriteExisting && await pathExists$1(targetDir)) return;
203
204
  try {
204
205
  await fs.rm(targetDir, {
205
206
  recursive: true,
@@ -212,7 +213,7 @@ async function resetDir(targetDir, overwriteExisting) {
212
213
  await fs.mkdir(targetDir, { recursive: true });
213
214
  }
214
215
  async function copyEntry(from, to, overwriteExisting, filter) {
215
- if (!overwriteExisting && await pathExists(to)) return;
216
+ if (!overwriteExisting && await pathExists$1(to)) return;
216
217
  try {
217
218
  await fs.cp(from, to, {
218
219
  recursive: true,
@@ -228,7 +229,7 @@ async function copyEntry(from, to, overwriteExisting, filter) {
228
229
  async function copyAssets(repoRoot, overwriteExisting) {
229
230
  for (const target of assetTargets$1) {
230
231
  const from = path$1.join(repoRoot, target);
231
- if (!await pathExists(from)) continue;
232
+ if (!await pathExists$1(from)) continue;
232
233
  const to = path$1.join(assetsDir, toPublishGitignorePath(target));
233
234
  const stats = await fs.stat(from);
234
235
  await copyEntry(from, to, overwriteExisting, target === ".husky" ? (src) => !/[\\/]_$/.test(src) : void 0);
@@ -238,7 +239,7 @@ async function copyAssets(repoRoot, overwriteExisting) {
238
239
  async function copyTemplates(repoRoot, overwriteExisting) {
239
240
  for (const template of templateChoices$1) {
240
241
  const from = path$1.join(repoRoot, "templates", template.source);
241
- if (!await pathExists(from)) continue;
242
+ if (!await pathExists$1(from)) continue;
242
243
  const to = path$1.join(templatesDir, template.source);
243
244
  const filter = (src) => !shouldSkipTemplatePath(from, src);
244
245
  await copyEntry(from, to, overwriteExisting, filter);
@@ -248,13 +249,77 @@ async function copyTemplates(repoRoot, overwriteExisting) {
248
249
  async function prepareAssets(options = {}) {
249
250
  const overwriteExisting = options.overwriteExisting ?? true;
250
251
  const repoRoot = path$1.resolve(packageDir, "..", "..");
251
- if (!await pathExists(path$1.join(repoRoot, "templates"))) return;
252
+ if (!await pathExists$1(path$1.join(repoRoot, "templates"))) return;
252
253
  await resetDir(assetsDir, overwriteExisting);
253
254
  await resetDir(templatesDir, overwriteExisting);
254
255
  await copyAssets(repoRoot, overwriteExisting);
255
256
  await copyTemplates(repoRoot, overwriteExisting);
256
257
  }
257
258
 
259
+ //#endregion
260
+ //#region src/runtime-assets.ts
261
+ const lockFileName = ".prepare-assets.lock";
262
+ const lockPollIntervalMs = 200;
263
+ const lockTimeoutMs = 3e4;
264
+ let ensurePromise = null;
265
+ async function pathExists(targetPath) {
266
+ try {
267
+ await access(targetPath);
268
+ return true;
269
+ } catch {
270
+ return false;
271
+ }
272
+ }
273
+ async function acquireLock(lockPath) {
274
+ try {
275
+ return await open(lockPath, "wx");
276
+ } catch (error) {
277
+ if (error.code === "EEXIST") return null;
278
+ throw error;
279
+ }
280
+ }
281
+ async function isPrepared() {
282
+ const checks = [
283
+ path.join(assetsDir, "AGENTS.md"),
284
+ path.join(assetsDir, "LICENSE"),
285
+ path.join(templatesDir, "unbuild")
286
+ ];
287
+ return (await Promise.all(checks.map(pathExists))).every(Boolean);
288
+ }
289
+ async function waitForPrepared() {
290
+ const deadline = Date.now() + lockTimeoutMs;
291
+ while (Date.now() < deadline) {
292
+ if (await isPrepared()) return true;
293
+ await new Promise((resolve) => setTimeout(resolve, lockPollIntervalMs));
294
+ }
295
+ return false;
296
+ }
297
+ async function runEnsure() {
298
+ if (await isPrepared()) return;
299
+ const lockPath = path.join(packageDir, lockFileName);
300
+ let lockHandle = await acquireLock(lockPath);
301
+ if (!lockHandle) {
302
+ if (await waitForPrepared()) return;
303
+ lockHandle = await acquireLock(lockPath);
304
+ if (!lockHandle) throw new Error(`Failed to prepare template assets: lock is held at ${lockPath}`);
305
+ }
306
+ try {
307
+ await prepareAssets({
308
+ overwriteExisting: false,
309
+ silent: true
310
+ });
311
+ } finally {
312
+ await lockHandle.close().catch(() => {});
313
+ await rm(lockPath, { force: true }).catch(() => {});
314
+ }
315
+ }
316
+ async function ensureTemplateAssetsPrepared() {
317
+ if (!ensurePromise) ensurePromise = runEnsure().finally(() => {
318
+ ensurePromise = null;
319
+ });
320
+ await ensurePromise;
321
+ }
322
+
258
323
  //#endregion
259
324
  //#region src/scaffold.ts
260
325
  async function isEmptyDir(dir) {
@@ -291,12 +356,13 @@ async function copyDirContents(sourceDir, targetDir, options = {}) {
291
356
  const targetName = renameEntry(entry.name);
292
357
  const to = path.join(targetDir, targetName);
293
358
  if (entry.isDirectory()) {
294
- await copyDirContents(from, to, {
359
+ const nestedOptions = {
295
360
  rootDir,
296
361
  filter,
297
- renameEntry,
298
- skipRootBasenames: options.skipRootBasenames
299
- });
362
+ renameEntry
363
+ };
364
+ if (options.skipRootBasenames) nestedOptions.skipRootBasenames = options.skipRootBasenames;
365
+ await copyDirContents(from, to, { ...nestedOptions });
300
366
  continue;
301
367
  }
302
368
  if (entry.isSymbolicLink()) {
@@ -307,18 +373,24 @@ async function copyDirContents(sourceDir, targetDir, options = {}) {
307
373
  await fs.cp(from, to);
308
374
  }
309
375
  }
376
+ function isDefaultTemplatesPath(sourceDir) {
377
+ const resolvedSource = path.resolve(sourceDir);
378
+ const resolvedTemplatesRoot = path.resolve(templatesDir);
379
+ return resolvedSource === resolvedTemplatesRoot || resolvedSource.startsWith(`${resolvedTemplatesRoot}${path.sep}`);
380
+ }
310
381
  async function scaffoldTemplate(options) {
311
382
  const { sourceDir, targetDir, ensureTargetDir = true, filter, renameEntry, skipRootBasenames } = options;
383
+ if (isDefaultTemplatesPath(sourceDir)) await ensureTemplateAssetsPrepared();
312
384
  if (ensureTargetDir) await fs.mkdir(targetDir, { recursive: true });
313
- await copyDirContents(sourceDir, targetDir, {
314
- rootDir: sourceDir,
315
- filter,
316
- renameEntry,
317
- skipRootBasenames
318
- });
385
+ const copyOptions = { rootDir: sourceDir };
386
+ if (filter) copyOptions.filter = filter;
387
+ if (renameEntry) copyOptions.renameEntry = renameEntry;
388
+ if (skipRootBasenames) copyOptions.skipRootBasenames = skipRootBasenames;
389
+ await copyDirContents(sourceDir, targetDir, copyOptions);
319
390
  }
320
391
  async function scaffoldWorkspace(options) {
321
392
  const { targetDir, templateKeys = [], assetsDir: assetsDir$1 = assetsDir, templatesDir: templatesDir$1 = templatesDir, includeAssets = true, targetMode = "prepare", force = false } = options;
393
+ if (includeAssets && path.resolve(assetsDir$1) === path.resolve(assetsDir) || templateKeys.length > 0 && path.resolve(templatesDir$1) === path.resolve(templatesDir)) await ensureTemplateAssetsPrepared();
322
394
  if (targetMode === "prepare") await prepareTargetDir(targetDir, { force });
323
395
  else if (targetMode === "ensure") await fs.mkdir(targetDir, { recursive: true });
324
396
  if (includeAssets) await copyDirContents(assetsDir$1, targetDir, { rootDir: assetsDir$1 });
@@ -352,4 +424,4 @@ const templateTargetMap = Object.fromEntries(templateChoices.map((item) => [item
352
424
  const templateMap = templateSourceMap;
353
425
 
354
426
  //#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 };
427
+ 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.11",
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
+ "@tanstack/vue-virtual": "^3.13.19",
22
23
  "@trpc/client": "^11.10.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": "^3.5.29",
28
29
  "vue-i18n": "^11.2.8",
29
- "vue-router": "^5.0.2"
30
+ "vue-router": "^5.0.3"
30
31
  },
31
32
  "devDependencies": {
32
- "@cloudflare/vite-plugin": "^1.25.0",
33
+ "@cloudflare/vite-plugin": "^1.25.5",
33
34
  "@hono/node-server": "^1.19.9",
34
35
  "@hono/trpc-server": "^0.4.2",
35
- "@tailwindcss/vite": "^4.1.18",
36
+ "@tailwindcss/vite": "^4.2.1",
36
37
  "@trpc/server": "^11.10.0",
37
38
  "@vitejs/plugin-vue": "^6.0.4",
38
39
  "@vitejs/plugin-vue-jsx": "^5.1.4",
39
40
  "@vue/tsconfig": "^0.8.1",
40
41
  "consola": "^3.4.2",
41
- "hono": "^4.11.9",
42
- "tailwindcss": "^4.1.18",
42
+ "hono": "^4.12.2",
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
47
  "vite-plugin-vue-devtools": "^8.0.6",
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.68.1",
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": {
@@ -53,8 +54,8 @@
53
54
  "devDependencies": {
54
55
  "@hono/node-server": "^1.19.9",
55
56
  "consola": "^3.4.2",
56
- "hono": "^4.11.9",
57
- "wrangler": "^4.65.0",
57
+ "hono": "^4.12.2",
58
+ "wrangler": "^4.68.1",
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
  }
@@ -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": {
@@ -46,10 +47,10 @@
46
47
  "@vitejs/plugin-vue": "^6.0.4",
47
48
  "@vue/test-utils": "^2.4.6",
48
49
  "@vue/tsconfig": "^0.8.1",
49
- "jsdom": "^28.0.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
  }