@icebreakers/monorepo-templates 0.1.9 → 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.
- package/assets/.vscode/settings.json +0 -3
- package/assets/AGENTS.md +50 -0
- package/assets/lint-staged.config.js +9 -2
- package/assets/package.json +9 -9
- package/assets-data.mjs +1 -0
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +90 -18
- 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 +12 -11
- package/templates/server/package.json +4 -3
- package/templates/tsdown/package.json +2 -1
- package/templates/tsup/package.json +2 -1
- package/templates/unbuild/package.json +2 -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 +5 -4
|
@@ -7,9 +7,6 @@
|
|
|
7
7
|
"source.fixAll.eslint": "explicit",
|
|
8
8
|
"source.organizeImports": "never"
|
|
9
9
|
},
|
|
10
|
-
"eslint.options": {
|
|
11
|
-
"flags": ["v10_config_lookup_from_file"]
|
|
12
|
-
},
|
|
13
10
|
// Silent the stylistic rules in you IDE, but still auto fix them
|
|
14
11
|
"eslint.rules.customizations": [
|
|
15
12
|
// close "style/* off"
|
package/assets/AGENTS.md
ADDED
|
@@ -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).
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
'*.{js,jsx,mjs,ts,tsx,mts
|
|
2
|
+
'*.{js,jsx,mjs,ts,tsx,mts}': [
|
|
3
3
|
'eslint --fix',
|
|
4
4
|
],
|
|
5
|
-
'*.
|
|
5
|
+
'*.vue': [
|
|
6
|
+
'eslint --fix',
|
|
7
|
+
'stylelint --fix --allow-empty-input',
|
|
8
|
+
],
|
|
9
|
+
'*.{json,md,mdx,html,yml,yaml}': [
|
|
6
10
|
// 'prettier --with-node-modules --ignore-path .prettierignore --write',
|
|
7
11
|
'eslint --fix',
|
|
8
12
|
],
|
|
13
|
+
'*.{css,scss,sass,less}': [
|
|
14
|
+
'stylelint --fix --allow-empty-input',
|
|
15
|
+
],
|
|
9
16
|
// for rust
|
|
10
17
|
// '*.rs': ['cargo fmt --'],
|
|
11
18
|
}
|
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.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.
|
|
49
|
+
"@commitlint/cli": "^20.4.2",
|
|
50
50
|
"@icebreakers/changelog-github": "^0.2.0",
|
|
51
|
-
"@icebreakers/commitlint-config": "^1.2.
|
|
52
|
-
"@icebreakers/eslint-config": "^1.6.
|
|
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.
|
|
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.
|
|
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.
|
|
72
|
-
"stylelint": "^17.
|
|
71
|
+
"rimraf": "^6.1.3",
|
|
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.
|
|
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
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
|
-
|
|
359
|
+
const nestedOptions = {
|
|
295
360
|
rootDir,
|
|
296
361
|
filter,
|
|
297
|
-
renameEntry
|
|
298
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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.
|
|
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
|
|
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
|
+
"@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.
|
|
26
|
+
"tailwind-merge": "^3.5.0",
|
|
26
27
|
"tailwind-variants": "^3.2.2",
|
|
27
|
-
"vue": "^3.5.
|
|
28
|
+
"vue": "^3.5.29",
|
|
28
29
|
"vue-i18n": "^11.2.8",
|
|
29
|
-
"vue-router": "^5.0.
|
|
30
|
+
"vue-router": "^5.0.3"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@cloudflare/vite-plugin": "^1.25.
|
|
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
|
|
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.
|
|
42
|
-
"tailwindcss": "^4.1
|
|
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.
|
|
49
|
-
"wrangler": "^4.
|
|
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.
|
|
57
|
-
"wrangler": "^4.
|
|
57
|
+
"hono": "^4.12.2",
|
|
58
|
+
"wrangler": "^4.68.1",
|
|
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": {
|
|
@@ -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.
|
|
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
|
}
|