@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.
- package/assets/AGENTS.md +51 -0
- package/assets/gitignore +2 -1
- package/assets/package.json +16 -16
- package/assets-data.mjs +1 -0
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +94 -31
- package/package.json +4 -4
- package/templates/cli/package.json +2 -1
- package/templates/client/AGENTS.md +28 -0
- package/templates/client/eslint.config.js +4 -1
- package/templates/client/package.json +18 -17
- package/templates/server/package.json +5 -4
- package/templates/tsdown/package.json +2 -1
- package/templates/tsup/package.json +2 -1
- package/templates/unbuild/package.json +2 -1
- package/templates/unbuild/src/utils.ts +3 -1
- package/templates/vitepress/package.json +6 -5
- package/templates/vitepress/tools/turborepo.md +1 -1
- package/templates/vitepress/tsconfig.json +15 -0
- package/templates/vitepress/why/js-cjs-mjs.md +1 -1
- package/templates/vitepress/why/modern/dts.md +5 -5
- package/templates/vitepress/why/what-is-npm-package.md +3 -3
- package/templates/vue-lib/package.json +6 -5
package/assets/AGENTS.md
ADDED
|
@@ -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
package/assets/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"type": "module",
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"private": true,
|
|
6
|
-
"packageManager": "pnpm@10.
|
|
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.
|
|
49
|
-
"@commitlint/cli": "^20.4.
|
|
50
|
-
"@icebreakers/changelog-github": "^0.2.
|
|
51
|
-
"@icebreakers/commitlint-config": "^1.2.
|
|
52
|
-
"@icebreakers/eslint-config": "^1.6.
|
|
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.
|
|
54
|
+
"@icebreakers/stylelint-config": "^2.0.3",
|
|
55
55
|
"@types/fs-extra": "^11.0.4",
|
|
56
|
-
"@types/node": "^25.
|
|
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.
|
|
63
|
-
"eslint": "^10.0.
|
|
62
|
+
"es-toolkit": "^1.45.1",
|
|
63
|
+
"eslint": "^10.0.3",
|
|
64
64
|
"execa": "^9.6.1",
|
|
65
|
-
"fs-extra": "^11.3.
|
|
65
|
+
"fs-extra": "^11.3.4",
|
|
66
66
|
"husky": "^9.1.7",
|
|
67
|
-
"lint-staged": "^16.2
|
|
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.
|
|
72
|
-
"stylelint": "^17.
|
|
73
|
-
"tsdown": "0.
|
|
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.
|
|
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
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(
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
353
|
+
const nestedOptions = {
|
|
295
354
|
rootDir,
|
|
296
355
|
filter,
|
|
297
|
-
renameEntry
|
|
298
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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.
|
|
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
|
|
43
|
-
"@inquirer/input": "^5.0.
|
|
44
|
-
"@inquirer/select": "^5.0
|
|
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
|
},
|
|
@@ -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:
|
|
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.
|
|
22
|
-
"@trpc/client": "^11.
|
|
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.
|
|
26
|
+
"tailwind-merge": "^3.5.0",
|
|
26
27
|
"tailwind-variants": "^3.2.2",
|
|
27
|
-
"vue": "^3.5.
|
|
28
|
-
"vue-i18n": "^11.
|
|
29
|
-
"vue-router": "^5.0.
|
|
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.
|
|
33
|
-
"@hono/node-server": "^1.19.
|
|
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
|
|
36
|
-
"@trpc/server": "^11.
|
|
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.
|
|
40
|
+
"@vue/tsconfig": "^0.9.0",
|
|
40
41
|
"consola": "^3.4.2",
|
|
41
|
-
"hono": "^4.
|
|
42
|
-
"tailwindcss": "^4.1
|
|
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.
|
|
47
|
+
"vite-plugin-vue-devtools": "^8.0.7",
|
|
47
48
|
"vite-tsconfig-paths": "^6.1.1",
|
|
48
|
-
"vue-tsc": "3.2.
|
|
49
|
-
"wrangler": "^4.
|
|
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.
|
|
55
|
+
"@hono/node-server": "^1.19.11",
|
|
55
56
|
"consola": "^3.4.2",
|
|
56
|
-
"hono": "^4.
|
|
57
|
-
"wrangler": "^4.
|
|
57
|
+
"hono": "^4.12.5",
|
|
58
|
+
"wrangler": "^4.71.0",
|
|
58
59
|
"zod": "^4.3.6"
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -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.
|
|
26
|
+
"vue": "^3.5.29"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@braintree/sanitize-url": "^7.1.2",
|
|
29
|
-
"@tailwindcss/vite": "^4.1
|
|
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.
|
|
35
|
-
"tailwindcss": "^4.1
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
49
|
-
"jsdom": "^28.
|
|
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.
|
|
53
|
-
"vue-tsc": "^3.2.
|
|
53
|
+
"vue": "^3.5.29",
|
|
54
|
+
"vue-tsc": "^3.2.5"
|
|
54
55
|
}
|
|
55
56
|
}
|