@adbayb/stack 3.4.0 → 3.4.1-next.1792495180470.df7f761
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/README.md
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
`Stack` allows creating an NPM project in a **quick**, **easy** and **opinionated** way with:
|
|
11
11
|
|
|
12
12
|
- A unified monorepo-driven and git-driven approach for all kinds of projects (single-purpose library, multiple libraries, application(s), ...)
|
|
13
|
-
- Support for several application templates (for now, only a default template is available, but others can be added later)
|
|
13
|
+
- Support for several application templates (for now, only a default template is available, but others can be added later).
|
|
14
14
|
- Standardized file structure including optional folder creation such as `examples`, `packages`, ...
|
|
15
|
-
- Enable all project lifecycle steps with commands from the setup, check, fix, build, and test to the versioning and release
|
|
16
|
-
- Built-in configuration preset setup (including Node/NPM runtime, Git ignore/hooks, [TypeScript](https://github.com/adbayb/stack/blob/main/stack/configs/typescript/), [Oxlint](https://github.com/adbayb/stack/blob/main/stack/configs/oxlint/), [Oxfmt](https://github.com/adbayb/stack/blob/main/stack/configs/oxfmt/), EditorConfig, Renovate, Changesets, and VSCode)
|
|
15
|
+
- Enable all project lifecycle steps with commands from the setup, check, fix, build, and test to the versioning and release.
|
|
16
|
+
- Built-in configuration preset setup (including Node/NPM runtime, Git ignore/hooks, [TypeScript](https://github.com/adbayb/stack/blob/main/stack/configs/typescript/), [Oxlint](https://github.com/adbayb/stack/blob/main/stack/configs/oxlint/), [Oxfmt](https://github.com/adbayb/stack/blob/main/stack/configs/oxfmt/), EditorConfig, Renovate, Changesets, and VSCode).
|
|
17
17
|
- A welcoming environment for users and contributors with the continuous integration setup, and the creation of `README.md`, `CONTRIBUTING.md`, and GitHub template files (including issue and pull request ones), ...
|
|
18
|
-
- An optimized development environment with command caching (no build needed if nothing changes)
|
|
18
|
+
- An optimized development environment with command caching (no build needed if nothing changes).
|
|
19
19
|
|
|
20
20
|
<br>
|
|
21
21
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createLogger, exec, termost } from "termost";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { existsSync, readdirSync } from "node:fs";
|
|
5
5
|
import { chmod, cp, mkdir, readFile, readdir, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
6
6
|
|
|
@@ -461,7 +461,12 @@ const label$3 = (message) => {
|
|
|
461
461
|
};
|
|
462
462
|
const cleanFiles = async (files) => {
|
|
463
463
|
await Promise.all(files.map(async (file) => {
|
|
464
|
-
|
|
464
|
+
const absolutePath = resolveFromWorkingDirectory(file);
|
|
465
|
+
const pathFromWorkingDirectory = relative(process.cwd(), absolutePath);
|
|
466
|
+
assert(pathFromWorkingDirectory !== "" && pathFromWorkingDirectory !== ".." && !pathFromWorkingDirectory.startsWith(`..${sep}`) && !isAbsolute(pathFromWorkingDirectory), () => {
|
|
467
|
+
return createError("stack clean", `Refusing to remove a path outside the working directory: ${file}`);
|
|
468
|
+
});
|
|
469
|
+
await rm(absolutePath, {
|
|
465
470
|
force: true,
|
|
466
471
|
recursive: true
|
|
467
472
|
});
|
|
@@ -472,16 +477,31 @@ const isDirectoryExistAndNotEmpty = (path) => {
|
|
|
472
477
|
};
|
|
473
478
|
const LINEBREAK_REGEXP = /\n|\r\n/u;
|
|
474
479
|
const retrieveIgnoredFiles = async () => {
|
|
475
|
-
return (await exec("git clean -fdXn")).split(LINEBREAK_REGEXP).
|
|
476
|
-
return PRESERVE_FILES.every((excludedFile) => {
|
|
477
|
-
return !cleanOutput.includes(excludedFile);
|
|
478
|
-
});
|
|
479
|
-
}).map((cleanOutput) => {
|
|
480
|
+
return (await exec("git clean -fdXn")).split(LINEBREAK_REGEXP).map((cleanOutput) => {
|
|
480
481
|
return cleanOutput.startsWith(REMOVABLE_FILE_PREFIX) ? cleanOutput.slice(REMOVABLE_FILE_PREFIX.length) : "";
|
|
481
|
-
}).filter(Boolean)
|
|
482
|
+
}).filter(Boolean).filter((file) => {
|
|
483
|
+
return !isPreservedPath(file);
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
/**
|
|
487
|
+
* Check whether `stack clean` must preserve a path.
|
|
488
|
+
*
|
|
489
|
+
* Preserve installed dependencies and local environment files, which may contain secrets and must
|
|
490
|
+
* never be removed by a generated-file cleanup.
|
|
491
|
+
*
|
|
492
|
+
* @param path - The repository-relative path to inspect.
|
|
493
|
+
* @returns `true` when the path or one of its segments is preserved.
|
|
494
|
+
*/
|
|
495
|
+
const isPreservedPath = (path) => {
|
|
496
|
+
return path.split(PATH_SEGMENT_SEPARATOR).some((segment) => {
|
|
497
|
+
return PRESERVED_SEGMENTS.some((pattern) => {
|
|
498
|
+
return pattern.test(segment);
|
|
499
|
+
});
|
|
500
|
+
});
|
|
482
501
|
};
|
|
483
502
|
const REMOVABLE_FILE_PREFIX = "Would remove ";
|
|
484
|
-
const
|
|
503
|
+
const PATH_SEGMENT_SEPARATOR = /[\\/]/u;
|
|
504
|
+
const PRESERVED_SEGMENTS = [/^node_modules$/u, /^\.env(?:\.|$)/u];
|
|
485
505
|
|
|
486
506
|
//#endregion
|
|
487
507
|
//#region src/commands/create.ts
|
|
@@ -786,8 +806,7 @@ const createInstallCommand = (program) => {
|
|
|
786
806
|
}).task({
|
|
787
807
|
label: label("Install `git.pre-commit` hook"),
|
|
788
808
|
async handler() {
|
|
789
|
-
|
|
790
|
-
await installGitHook("pre-commit", `files=${changedFiles}; if [ -n "$files" ]; then ${getStackCommand("fix $files")}; fi && git add -A`);
|
|
809
|
+
await installGitHook("pre-commit", PRE_COMMIT_HOOK);
|
|
791
810
|
}
|
|
792
811
|
}).task({
|
|
793
812
|
label: label("Install `git.commit-msg` hook"),
|
|
@@ -799,6 +818,7 @@ const createInstallCommand = (program) => {
|
|
|
799
818
|
const label = (message) => {
|
|
800
819
|
return `${message} 📲`;
|
|
801
820
|
};
|
|
821
|
+
const PRE_COMMIT_HOOK = String.raw`node -e 'const { execFileSync } = require("node:child_process"); const files = execFileSync("git", ["diff", "--cached", "--name-only", "-z", "--diff-filter=ACMR"], { encoding: "utf8" }).split("\0").filter(Boolean); if (files.length > 0) { execFileSync("pnpm", ["stack", "fix", ...files], { stdio: "inherit" }); }' && git add -A`;
|
|
802
822
|
const installGitHook = async (hook, content) => {
|
|
803
823
|
assert(existsSync(resolveFromWorkingDirectory(".git")), () => {
|
|
804
824
|
return createError("stack install", "No `.git` directory found. Run `git init` first.");
|
|
@@ -908,7 +928,7 @@ const createWatchCommand = (program) => {
|
|
|
908
928
|
|
|
909
929
|
//#endregion
|
|
910
930
|
//#region package.json
|
|
911
|
-
var version = "3.4.
|
|
931
|
+
var version = "3.4.1-next.1792495180470.df7f761";
|
|
912
932
|
|
|
913
933
|
//#endregion
|
|
914
934
|
//#region src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adbayb/stack",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.1-next.1792495180470.df7f761",
|
|
4
4
|
"description": "My opinionated JavaScript-based toolchain",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"development",
|
|
@@ -53,26 +53,28 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@changesets/changelog-github": "^1.0.1",
|
|
55
55
|
"@changesets/cli": "^3.0.3",
|
|
56
|
-
"@commitlint/cli": "^21.2.
|
|
57
|
-
"@commitlint/config-conventional": "^21.2.
|
|
56
|
+
"@commitlint/cli": "^21.2.4",
|
|
57
|
+
"@commitlint/config-conventional": "^21.2.4",
|
|
58
58
|
"@e18e/eslint-plugin": "^0.8.1",
|
|
59
59
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
60
60
|
"eslint-plugin-perfectionist": "^5.12.1",
|
|
61
|
-
"oxfmt": "^0.
|
|
62
|
-
"oxlint": "^1.
|
|
61
|
+
"oxfmt": "^0.73.0",
|
|
62
|
+
"oxlint": "^1.88.0",
|
|
63
63
|
"oxlint-tsgolint": "^7.0.2004",
|
|
64
64
|
"termost": "^2.0.0",
|
|
65
|
-
"turbo": "^2.11.
|
|
65
|
+
"turbo": "^2.11.9",
|
|
66
66
|
"typescript": "^7.0.2"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node": "24.19.2",
|
|
70
|
-
"quickbundle": "3.2.2"
|
|
70
|
+
"quickbundle": "3.2.2",
|
|
71
|
+
"vitest": "5.0.5"
|
|
71
72
|
},
|
|
72
73
|
"scripts": {
|
|
73
74
|
"build": "quickbundle build",
|
|
74
75
|
"prestart": "pnpm build",
|
|
75
76
|
"start": "./bin/index.js",
|
|
77
|
+
"test": "vitest run",
|
|
76
78
|
"watch": "quickbundle watch"
|
|
77
79
|
}
|
|
78
80
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This repo is `{{projectName}}`: {{projectDescription}}.
|
|
4
4
|
Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` binary.
|
|
5
5
|
|
|
6
|
-
> Extend `Project overview`, `Code style guidelines`, and `Security considerations` as packages are added and project-specific rules emerge.
|
|
6
|
+
> Extend `Project overview`, `Commands`, `Testing`, `Code style guidelines`, and `Security considerations` as packages are added and project-specific rules emerge.
|
|
7
7
|
|
|
8
8
|
## Project overview
|
|
9
9
|
|
|
@@ -14,7 +14,7 @@ Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` bi
|
|
|
14
14
|
- Cross-package imports only via workspace package names: no relative imports across `applications`/`libraries` boundaries.
|
|
15
15
|
- `examples/*/`: private playground apps (excluded from build/check orchestration).
|
|
16
16
|
- `tools/*/`: private local tooling packages.
|
|
17
|
-
- Root configs
|
|
17
|
+
- Root configs: `oxlint.config.ts` and `oxfmt.config.ts` re-export `@adbayb/stack` presets; `tsconfig.json` extends the shared TypeScript preset; `turbo.json` is the local pipeline config. Extend the shared presets, don't replace them.
|
|
18
18
|
- pnpm workspace members: `applications/*`, `libraries/*`, `examples/*`, `tools/*` (`pnpm-workspace.yaml`, `saveExact: true`).
|
|
19
19
|
- If a package needs tailored guidance, add a nested `AGENTS.md` inside it: agents read the nearest file, so it takes precedence over this root one.
|
|
20
20
|
|
|
@@ -22,25 +22,25 @@ Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` bi
|
|
|
22
22
|
|
|
23
23
|
Follow this order:
|
|
24
24
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- `stack install` writes `.git/hooks/pre-commit` (`stack fix
|
|
25
|
+
- Check `devEngines` in root `package.json` (`runtime.node` and `packageManager.pnpm`) and confirm the local Node and pnpm versions match it before running anything.
|
|
26
|
+
- `stack create` already runs `pnpm install` and `stack install`. After cloning an existing project instead, run `pnpm install --frozen-lockfile` (its lifecycle `install` script runs `stack install`).
|
|
27
|
+
- `stack install` writes `.git/hooks/pre-commit` (runs `stack fix` with staged files as arguments, then stages changes with `git add -A`) and `commit-msg` (`stack check --filter commit`). Re-run it if hooks are missing.
|
|
28
28
|
|
|
29
29
|
## Commands
|
|
30
30
|
|
|
31
31
|
Root scripts (`package.json`) are thin wrappers around `stack`, which wraps turbo / oxlint / oxfmt / commitlint / changesets / quickbundle. Always use these, not raw tools:
|
|
32
32
|
|
|
33
|
-
- `pnpm build` (`stack build` → `turbo run build`, cached, outputs `dist/**`, builder is `quickbundle build`)
|
|
34
|
-
- `pnpm check` (`stack check`: builds first excluding examples, then dependency + formatting + code checks (commit checked separately via `--filter commit` or the `commit-msg` hook); always builds first unless `--filter commit`)
|
|
35
|
-
- `pnpm fix` (`stack fix`: auto-fixable oxlint `--fix-dangerously` + oxfmt `--write`)
|
|
36
|
-
- `pnpm test` (`stack test` → `turbo run test`, `dependsOn: build`, so build runs first)
|
|
37
|
-
- `pnpm clean` (`stack clean`: deletes
|
|
38
|
-
-
|
|
39
|
-
- Release via changesets: `pnpm release:changelog` (`--changelog`), `release:version` (`--bump`), `release:snapshot` (`--snapshot`), `release:publish` (`--publish
|
|
33
|
+
- `pnpm build` (`stack build` → `turbo run build`, cached, outputs `dist/**`, builder is `quickbundle build`).
|
|
34
|
+
- `pnpm check` (`stack check`: builds first excluding examples, then dependency + formatting + code checks (commit checked separately via `--filter commit` or the `commit-msg` hook); always builds first unless `--filter commit`).
|
|
35
|
+
- `pnpm fix` (`stack fix`: auto-fixable oxlint `--fix-dangerously` + oxfmt `--write`).
|
|
36
|
+
- `pnpm test` (`stack test` → `turbo run test`, `dependsOn: build`, so build runs first).
|
|
37
|
+
- `pnpm clean` (`stack clean`: deletes git-ignored files such as build output and caches, including `node_modules/.cache`; preserves `node_modules` and `.env*`).
|
|
38
|
+
- Other untracked-but-ignored files are removed too; don't hand-delete `dist/`, use `stack clean`.
|
|
39
|
+
- Release via changesets: `pnpm release:changelog` (`--changelog`), `release:version` (`--bump`), `release:snapshot` (`--snapshot`), `release:publish` (`--publish`; documented release flow only).
|
|
40
40
|
|
|
41
|
-
Focused verification: `stack check --filter <code|formatting|dependency|commit> [files...]` and `stack fix [files...]
|
|
41
|
+
Focused verification: `stack check --filter <code|formatting|dependency|commit> [files...]` and `stack fix [files...]`; for focused tests, `pnpm --filter <workspace-package> test` re-runs only that workspace's suite while iterating. CI order (`.github/workflows/workflow.yml`) is `install --frozen-lockfile` → `build` → `check` → `test`.
|
|
42
42
|
|
|
43
|
-
### Testing
|
|
43
|
+
### Testing
|
|
44
44
|
|
|
45
45
|
- Run `pnpm check` and `pnpm test` before finishing; fix failures until green.
|
|
46
46
|
- Add or update tests for the code you change, even if nobody asked, provided each test earns its place (covers behavior, not coverage).
|
|
@@ -48,60 +48,18 @@ Focused verification: `stack check --filter <code|formatting|dependency|commit>
|
|
|
48
48
|
## Code style guidelines
|
|
49
49
|
|
|
50
50
|
Formatting is enforced by the shared `@adbayb/stack` presets (re-exported by root `oxlint.config.ts`/`oxfmt.config.ts`): comply via `stack fix`, never hand-format.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
- KISS (Keep It Simple, Stupid) over cleverness; DRY (Don't Repeat Yourself) at knowledge level (rule of three before abstracting duplicated logic).
|
|
54
|
-
- Minimal API surface (YAGNI: You Aren't Gonna Need It): expose only what's needed now; small explicit functions, narrow interfaces.
|
|
55
|
-
- Keep symbols local; promote to the lowest shared scope all consumers already depend on only for a second existing consumer (planned consumers and same-package tests excluded; published-API consumers included).
|
|
56
|
-
- Exception: a contract both sides of a boundary must agree on may be shared right away. If tooling prevents sharing it, duplicate it and guard it with a test.
|
|
57
|
-
- Before exporting across a boundary, check who imports it. Prefer sharing contracts over implementations. Never export a counterpart (e.g. a reader for a writer) ahead of its first consumer.
|
|
58
|
-
- POLA (Principle of Least Astonishment): names and behavior as expected; fail fast at boundaries, no surprise side effects.
|
|
59
|
-
- High cohesion, low coupling:
|
|
60
|
-
- Keep code that changes together, or serves one responsibility, in one place.
|
|
61
|
-
- Keep module/slice/package dependencies narrow, intentional, toward more stable lower-level code. No cycles.
|
|
62
|
-
- Maintainable, evolvable design: apply the following only when it solves a present need (YAGNI/KISS/rule of three still win; no speculative layers or patterns in small modules).
|
|
63
|
-
- SOLID, especially Dependency Inversion (small interfaces, injected implementations), Single Responsibility (one purpose per unit), Open/Closed (extend via composition, not inheritance or branch edits), Interface Segregation (narrow, consumer-shaped interfaces). Liskov when subtyping.
|
|
64
|
-
- Design patterns when the problem matches: Adapter (wrap external APIs behind our contract), Facade (simple public API over complex internals), Strategy (swappable behavior per environment/config), Factory (creation varying by config or dependencies), Decorator (add cross-cutting behavior without touching the wrapped unit). Don't name a pattern in code unless it helps readers.
|
|
65
|
-
- Hexagonal/ports-and-adapters at I/O boundaries (CLI args, env, files, network, storage, third-party SDKs): pure testable domain logic without I/O; I/O behind ports implemented by edge adapters; dependencies point inward.
|
|
66
|
-
- Testing: verify behavior, keep the suite lean.
|
|
67
|
-
- Test functional behavior through the public interface, not implementation details (internal calls, private structure, call order unless it is the contract). A refactor that keeps behavior must not break tests.
|
|
68
|
-
- Every test must earn its place: never write tests to raise line count or coverage.
|
|
69
|
-
- Consolidate overlapping cases (e.g. `test.each`); remove redundant, obsolete or implementation-coupled tests in files you edit, listing removals and reasons in the change summary.
|
|
70
|
-
- One behavior per test, named by scenario and expectation; keep tests FIRST (fast, independent, repeatable, self-validating); cover boundaries and failure paths, not just the happy path.
|
|
71
|
-
- Code organization: public-facing interface first (exports before implementation in source files, describes/tests before helpers in test files; no use-before-define of `const` helpers at module load). Order top-down (callers above callees); public/exported APIs carry a one-line TSDoc (`/** */`) stating the contract, with `@param`/`@returns` only when the description adds info beyond the types.
|
|
72
|
-
- Consistent naming: pick one verb per contract, use everywhere (POLA), never add synonym for something already named.
|
|
73
|
-
- Queries (never throw; `undefined`/empty when missing): `get` (lookup by identity), `find` (search by criteria/filter), `exists`/`count`. A throwing lookup needs its own intention verb (e.g. `getOrThrow`). Collections use a plural noun, never an `All` infix (`getOrders`, `findOverdueOrders`, not `getAllOrders`).
|
|
74
|
-
- Commands: `create`/`update`/`remove` for lifecycle; intention verbs for domain behavior (`refundOrder`, not `setStatus`).
|
|
75
|
-
- Banned → use: `fetch`/`retrieve`/`load`/`read` → `get`, `query`(verb) → `find`/`exists`, `delete`/`clear` → `remove`, `add`/`insert`/`save` → `create`/`update`, `set` → intention verb (DTOs/builders and React state setters exempt), `process`/`handle`/`manage`/`do` → specific intention verb, `data`/`info`/`util` → domain noun. Third-party and platform APIs (e.g. `fetch`) keep their own names. Apply to new and edited code; don't rename existing exported symbols just for this.
|
|
76
|
-
- Booleans: `is*`/`has*`/`can*`. Handlers: `on<Event>`.
|
|
77
|
-
- Generic utilities: `input`/`...inputs` for domain-less parameters, `output` for domain-less results (instead of `data`/`arg`/`x`); domain meaning always wins (`userId`, never `input`).
|
|
78
|
-
- Boundary contracts: bespoke input/output shapes take `<FunctionName>Input` / `<FunctionName>Output`; reuse domain types otherwise, never alias per function.
|
|
79
|
-
- Functions verb-first (`findOverdueOrders`), classes/types nouns (`OrderRefunder`), no stutter (`orders.get(id)` not `orderRepo.getOrder`).
|
|
80
|
-
- Editing a file: match verbs already used in that slice, don't add a second synonym.
|
|
81
|
-
- Type inference: skip explicit return/output types when TypeScript can infer them; annotate only when a stricter type than inferred is wanted (e.g. an enum instead of `string`) or when an exported API contract should be explicit.
|
|
82
|
-
- Explicit logic: name the middle steps (explanatory variables, encapsulated boolean conditions); prefer positive conditionals. Every name must earn its place: single-use consts without meaning, await, narrowing, or literal get inlined. Make dependencies physical (import, parameter, injection); never require a call order or rely on ambient execution order. Every default, limit, timeout, and retry count is a named constant with a stated reason at module top.
|
|
83
|
-
- Strict types: never silence the type checker (`as any`, `@ts-ignore`/`@ts-expect-error`, non-null `!`); absence is `undefined`, never `null`. Size names to their scope; no type prefixes, encodings, or `Manager`/`Helper`/`Util` holders.
|
|
84
|
-
- Error handling: prefer Result objects from `@open-vanilla/result` over throwing for normal control flow; map foreign exceptions to failed Results at the boundary. Expected absence stays `undefined`/empty (queries never throw). New and edited code only; don't migrate existing throw-based code.
|
|
85
|
-
- Avoid comments: prefer self-explanatory code; comment only the why (complex logic, non-obvious workflows, deliberately preserved ambiguous patterns). No narration of readable code, no commented-out code.
|
|
86
|
-
- Neutral artifacts: generated text (source, docs, comments, commit messages, changelogs) must not contain symbols that identify model contribution.
|
|
87
|
-
- Never emit em-dashes (`—`, U+2014) anywhere; restructure the sentence or use a hyphen (`-`), colon, comma, or parentheses instead.
|
|
88
|
-
- Never emit a decorative en-dash (`–`, U+2013) or double hyphen; use a hyphen (ranges fine).
|
|
89
|
-
- Never emit invisible unicode (U+00A0, U+200B-U+200D, stray BOM); use a regular space or delete.
|
|
90
|
-
- Curly quotes and the ellipsis glyph (`…`, U+2026) are fine in prose but forbidden in code, comments, and copy-pasteable samples (they break pasted code).
|
|
91
|
-
- Same for other decorative non-ASCII punctuation.
|
|
92
|
-
- Apply to text you write or edit; don't rewrite existing files just for this.
|
|
93
|
-
- Avoid assumptions: when the approach is ambiguous and a wrong guess is costly to undo, ask before proceeding. Otherwise pick the sensible default and state it.
|
|
94
|
-
- Boy Scout Rule: leave code better than found. In edited files, make small, safe, behavior-preserving improvements (no exported renames) and mention them in the change summary. Larger or cross-file cleanup goes in a separate change.
|
|
51
|
+
Design and style rules have one source: the `software-design` skill (SKILL.md plus references). Load the repository copy when present; otherwise install it (`pnpm dlx skills add adbayb/stack --skill software-design -g`); on conflict, the skill wins its defaults and this section carries only repo deltas (none today: add a repo-specific design rule here when one emerges). If the skill cannot be loaded, follow existing repository conventions and ask before structural changes.
|
|
95
52
|
|
|
96
53
|
## Security considerations
|
|
97
54
|
|
|
98
|
-
- Never commit secrets (tokens, credentials)
|
|
55
|
+
- Never commit secrets (tokens, credentials). Use environment variables and keep `.env*` git-ignored. Never log or print secrets; keep them out of error messages.
|
|
99
56
|
- Validate untrusted input (CLI args, env, files, network) once at the boundary; fail fast with actionable errors.
|
|
100
|
-
- Never interpolate untrusted input into shell commands
|
|
101
|
-
- Keep file writes scoped to the narrowest path set; destructive operations (`clean`)
|
|
102
|
-
-
|
|
57
|
+
- Never interpolate untrusted input into shell commands. Use parameterized exec helpers; never raw shell strings.
|
|
58
|
+
- Keep file writes scoped to the narrowest path set; destructive operations (`clean`) remove only git-ignored paths and generated cache directories, preserve `node_modules` and `.env*`, and refuse paths outside the working directory.
|
|
59
|
+
- Run `pnpm release:publish` (or any changeset publish) only through the documented release flow on a clean, CI-green tree; never publish ad hoc.
|
|
60
|
+
- Pin devDependencies exactly (`saveExact: true`, or `workspace:*` for local packages). Prefix `dependencies` and `peerDependencies` with a caret (or `workspace:^` for local dependencies; peers stay explicit). `stack check` verifies these rules. Review automated dependency updates (Renovate) before merging.
|
|
103
61
|
|
|
104
62
|
## PR instructions
|
|
105
63
|
|
|
106
|
-
-
|
|
64
|
+
- PR title follows Conventional Commits (checked by `conventional_commit.yml`); commit messages follow Conventional Commits (commitlint + `commit-msg` hook). CI must be green before merge.
|
|
107
65
|
- Before requesting review: `pnpm check` passes, valuable tests added, changelog entry included for non-internal changes (`pnpm release:changelog`).
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
|
|
3
3
|
A collection of examples to:
|
|
4
4
|
|
|
5
|
-
- Showcase the artifacts' usage (mainly for libraries)
|
|
6
|
-
- Source some scenarios for testing purposes (test cases)
|
|
5
|
+
- Showcase the artifacts' usage (mainly for libraries).
|
|
6
|
+
- Source some scenarios for testing purposes (test cases).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This repo is `{{projectName}}`: {{projectDescription}}.
|
|
4
4
|
Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` binary.
|
|
5
5
|
|
|
6
|
-
> Extend `Project overview`, `Code style guidelines`, and `Security considerations` as packages are added and project-specific rules emerge.
|
|
6
|
+
> Extend `Project overview`, `Commands`, `Testing`, `Code style guidelines`, and `Security considerations` as packages are added and project-specific rules emerge.
|
|
7
7
|
|
|
8
8
|
## Project overview
|
|
9
9
|
|
|
@@ -12,32 +12,33 @@ Scaffolded with `@adbayb/stack`. All workflow scripts delegate to the `stack` bi
|
|
|
12
12
|
- `{{projectName}}/`: the publishable package (`src/index.ts` entry, `src/*.test.ts` colocated tests via `vitest`, `package.json` `exports` point to `dist/**`, built with `quickbundle`).
|
|
13
13
|
- `examples/*/`: private playground apps consuming `{{projectName}}` via `workspace:` (excluded from build/check orchestration).
|
|
14
14
|
- `tools/*/`: private local tooling packages.
|
|
15
|
-
- Root configs
|
|
15
|
+
- Root configs: `oxlint.config.ts` and `oxfmt.config.ts` re-export `@adbayb/stack` presets; `tsconfig.json` extends the shared TypeScript preset; `turbo.json` is the local pipeline config. Extend the shared presets, don't replace them.
|
|
16
16
|
- pnpm workspace members: `{{projectName}}`, `examples/*`, `tools/*` (`pnpm-workspace.yaml`, `saveExact: true`).
|
|
17
|
+
- If a package needs tailored guidance, add a nested `AGENTS.md` inside it: agents read the nearest file, so it takes precedence over this root one.
|
|
17
18
|
|
|
18
19
|
## Bootstrap
|
|
19
20
|
|
|
20
21
|
Follow this order:
|
|
21
22
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
- `stack install` writes `.git/hooks/pre-commit` (`stack fix
|
|
23
|
+
- Check `devEngines` in root `package.json` (`runtime.node` and `packageManager.pnpm`) and confirm the local Node and pnpm versions match it before running anything.
|
|
24
|
+
- `stack create` already runs `pnpm install` and `stack install`. After cloning an existing project instead, run `pnpm install --frozen-lockfile` (its lifecycle `install` script runs `stack install`).
|
|
25
|
+
- `stack install` writes `.git/hooks/pre-commit` (runs `stack fix` with staged files as arguments, then stages changes with `git add -A`) and `commit-msg` (`stack check --filter commit`). Re-run it if hooks are missing.
|
|
25
26
|
|
|
26
27
|
## Commands
|
|
27
28
|
|
|
28
29
|
Root scripts (`package.json`) are thin wrappers around `stack`, which wraps turbo / oxlint / oxfmt / commitlint / changesets / quickbundle. Always use these, not raw tools:
|
|
29
30
|
|
|
30
|
-
- `pnpm build` (`stack build` → `turbo run build`, cached, outputs `dist/**`, builder is `quickbundle build`)
|
|
31
|
-
- `pnpm check` (`stack check`: builds first excluding examples, then dependency + formatting + code checks (commit checked separately via `--filter commit` or the `commit-msg` hook); always builds first unless `--filter commit`)
|
|
32
|
-
- `pnpm fix` (`stack fix`: auto-fixable oxlint `--fix-dangerously` + oxfmt `--write`)
|
|
33
|
-
- `pnpm test` (`stack test` → `turbo run test`, `dependsOn: build`, so build runs first)
|
|
34
|
-
- `pnpm clean` (`stack clean`: deletes
|
|
35
|
-
-
|
|
36
|
-
- Release via changesets: `pnpm release:changelog` (`--changelog`), `release:version` (`--bump`), `release:snapshot` (`--snapshot`), `release:publish` (`--publish
|
|
31
|
+
- `pnpm build` (`stack build` → `turbo run build`, cached, outputs `dist/**`, builder is `quickbundle build`).
|
|
32
|
+
- `pnpm check` (`stack check`: builds first excluding examples, then dependency + formatting + code checks (commit checked separately via `--filter commit` or the `commit-msg` hook); always builds first unless `--filter commit`).
|
|
33
|
+
- `pnpm fix` (`stack fix`: auto-fixable oxlint `--fix-dangerously` + oxfmt `--write`).
|
|
34
|
+
- `pnpm test` (`stack test` → `turbo run test`, `dependsOn: build`, so build runs first).
|
|
35
|
+
- `pnpm clean` (`stack clean`: deletes git-ignored files such as build output and caches, including `node_modules/.cache`; preserves `node_modules` and `.env*`).
|
|
36
|
+
- Other untracked-but-ignored files are removed too; don't hand-delete `dist/`, use `stack clean`.
|
|
37
|
+
- Release via changesets: `pnpm release:changelog` (`--changelog`), `release:version` (`--bump`), `release:snapshot` (`--snapshot`), `release:publish` (`--publish`; documented release flow only).
|
|
37
38
|
|
|
38
|
-
Focused verification: `stack check --filter <code|formatting|dependency|commit> [files...]` and `stack fix [files...]
|
|
39
|
+
Focused verification: `stack check --filter <code|formatting|dependency|commit> [files...]` and `stack fix [files...]`; for focused tests, `pnpm --filter <workspace-package> test` re-runs only that workspace's suite while iterating. CI order (`.github/workflows/workflow.yml`) is `install --frozen-lockfile` → `build` → `check` → `test`.
|
|
39
40
|
|
|
40
|
-
### Testing
|
|
41
|
+
### Testing
|
|
41
42
|
|
|
42
43
|
- Run `pnpm check` and `pnpm test` before finishing; fix failures until green.
|
|
43
44
|
- Add or update tests for the code you change, even if nobody asked, provided each test earns its place (covers behavior, not coverage).
|
|
@@ -45,60 +46,18 @@ Focused verification: `stack check --filter <code|formatting|dependency|commit>
|
|
|
45
46
|
## Code style guidelines
|
|
46
47
|
|
|
47
48
|
Formatting is enforced by the shared `@adbayb/stack` presets (re-exported by root `oxlint.config.ts`/`oxfmt.config.ts`): comply via `stack fix`, never hand-format.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- KISS (Keep It Simple, Stupid) over cleverness; DRY (Don't Repeat Yourself) at knowledge level (rule of three before abstracting duplicated logic).
|
|
51
|
-
- Minimal API surface (YAGNI: You Aren't Gonna Need It): expose only what's needed now; small explicit functions, narrow interfaces.
|
|
52
|
-
- Keep symbols local; promote to the lowest shared scope all consumers already depend on only for a second existing consumer (planned consumers and same-package tests excluded; published-API consumers included).
|
|
53
|
-
- Exception: a contract both sides of a boundary must agree on may be shared right away. If tooling prevents sharing it, duplicate it and guard it with a test.
|
|
54
|
-
- Before exporting across a boundary, check who imports it. Prefer sharing contracts over implementations. Never export a counterpart (e.g. a reader for a writer) ahead of its first consumer.
|
|
55
|
-
- POLA (Principle of Least Astonishment): names and behavior as expected; fail fast at boundaries, no surprise side effects.
|
|
56
|
-
- High cohesion, low coupling:
|
|
57
|
-
- Keep code that changes together, or serves one responsibility, in one place.
|
|
58
|
-
- Keep module/slice/package dependencies narrow, intentional, toward more stable lower-level code. No cycles.
|
|
59
|
-
- Maintainable, evolvable design: apply the following only when it solves a present need (YAGNI/KISS/rule of three still win; no speculative layers or patterns in small modules).
|
|
60
|
-
- SOLID, especially Dependency Inversion (small interfaces, injected implementations), Single Responsibility (one purpose per unit), Open/Closed (extend via composition, not inheritance or branch edits), Interface Segregation (narrow, consumer-shaped interfaces). Liskov when subtyping.
|
|
61
|
-
- Design patterns when the problem matches: Adapter (wrap external APIs behind our contract), Facade (simple public API over complex internals), Strategy (swappable behavior per environment/config), Factory (creation varying by config or dependencies), Decorator (add cross-cutting behavior without touching the wrapped unit). Don't name a pattern in code unless it helps readers.
|
|
62
|
-
- Hexagonal/ports-and-adapters at I/O boundaries (CLI args, env, files, network, storage, third-party SDKs): pure testable domain logic without I/O; I/O behind ports implemented by edge adapters; dependencies point inward.
|
|
63
|
-
- Testing: verify behavior, keep the suite lean.
|
|
64
|
-
- Test functional behavior through the public interface, not implementation details (internal calls, private structure, call order unless it is the contract). A refactor that keeps behavior must not break tests.
|
|
65
|
-
- Every test must earn its place: never write tests to raise line count or coverage.
|
|
66
|
-
- Consolidate overlapping cases (e.g. `test.each`); remove redundant, obsolete or implementation-coupled tests in files you edit, listing removals and reasons in the change summary.
|
|
67
|
-
- One behavior per test, named by scenario and expectation; keep tests FIRST (fast, independent, repeatable, self-validating); cover boundaries and failure paths, not just the happy path.
|
|
68
|
-
- Code organization: public-facing interface first (exports before implementation in source files, describes/tests before helpers in test files; no use-before-define of `const` helpers at module load). Order top-down (callers above callees); public/exported APIs carry a one-line TSDoc (`/** */`) stating the contract, with `@param`/`@returns` only when the description adds info beyond the types.
|
|
69
|
-
- Consistent naming: pick one verb per contract, use everywhere (POLA), never add synonym for something already named.
|
|
70
|
-
- Queries (never throw; `undefined`/empty when missing): `get` (lookup by identity), `find` (search by criteria/filter), `exists`/`count`. A throwing lookup needs its own intention verb (e.g. `getOrThrow`). Collections use a plural noun, never an `All` infix (`getOrders`, `findOverdueOrders`, not `getAllOrders`).
|
|
71
|
-
- Commands: `create`/`update`/`remove` for lifecycle; intention verbs for domain behavior (`refundOrder`, not `setStatus`).
|
|
72
|
-
- Banned → use: `fetch`/`retrieve`/`load`/`read` → `get`, `query`(verb) → `find`/`exists`, `delete`/`clear` → `remove`, `add`/`insert`/`save` → `create`/`update`, `set` → intention verb (DTOs/builders and React state setters exempt), `process`/`handle`/`manage`/`do` → specific intention verb, `data`/`info`/`util` → domain noun. Third-party and platform APIs (e.g. `fetch`) keep their own names. Apply to new and edited code; don't rename existing exported symbols just for this.
|
|
73
|
-
- Booleans: `is*`/`has*`/`can*`. Handlers: `on<Event>`.
|
|
74
|
-
- Generic utilities: `input`/`...inputs` for domain-less parameters, `output` for domain-less results (instead of `data`/`arg`/`x`); domain meaning always wins (`userId`, never `input`).
|
|
75
|
-
- Boundary contracts: bespoke input/output shapes take `<FunctionName>Input` / `<FunctionName>Output`; reuse domain types otherwise, never alias per function.
|
|
76
|
-
- Functions verb-first (`findOverdueOrders`), classes/types nouns (`OrderRefunder`), no stutter (`orders.get(id)` not `orderRepo.getOrder`).
|
|
77
|
-
- Editing a file: match verbs already used in that slice, don't add a second synonym.
|
|
78
|
-
- Type inference: skip explicit return/output types when TypeScript can infer them; annotate only when a stricter type than inferred is wanted (e.g. an enum instead of `string`) or when an exported API contract should be explicit.
|
|
79
|
-
- Explicit logic: name the middle steps (explanatory variables, encapsulated boolean conditions); prefer positive conditionals. Every name must earn its place: single-use consts without meaning, await, narrowing, or literal get inlined. Make dependencies physical (import, parameter, injection); never require a call order or rely on ambient execution order. Every default, limit, timeout, and retry count is a named constant with a stated reason at module top.
|
|
80
|
-
- Strict types: never silence the type checker (`as any`, `@ts-ignore`/`@ts-expect-error`, non-null `!`); absence is `undefined`, never `null`. Size names to their scope; no type prefixes, encodings, or `Manager`/`Helper`/`Util` holders.
|
|
81
|
-
- Error handling: prefer Result objects from `@open-vanilla/result` over throwing for normal control flow; map foreign exceptions to failed Results at the boundary. Expected absence stays `undefined`/empty (queries never throw). New and edited code only; don't migrate existing throw-based code.
|
|
82
|
-
- Avoid comments: prefer self-explanatory code; comment only the why (complex logic, non-obvious workflows, deliberately preserved ambiguous patterns). No narration of readable code, no commented-out code.
|
|
83
|
-
- Neutral artifacts: generated text (source, docs, comments, commit messages, changelogs) must not contain symbols that identify model contribution.
|
|
84
|
-
- Never emit em-dashes (`—`, U+2014) anywhere; restructure the sentence or use a hyphen (`-`), colon, comma, or parentheses instead.
|
|
85
|
-
- Never emit a decorative en-dash (`–`, U+2013) or double hyphen; use a hyphen (ranges fine).
|
|
86
|
-
- Never emit invisible unicode (U+00A0, U+200B-U+200D, stray BOM); use a regular space or delete.
|
|
87
|
-
- Curly quotes and the ellipsis glyph (`…`, U+2026) are fine in prose but forbidden in code, comments, and copy-pasteable samples (they break pasted code).
|
|
88
|
-
- Same for other decorative non-ASCII punctuation.
|
|
89
|
-
- Apply to text you write or edit; don't rewrite existing files just for this.
|
|
90
|
-
- Avoid assumptions: when the approach is ambiguous and a wrong guess is costly to undo, ask before proceeding. Otherwise pick the sensible default and state it.
|
|
91
|
-
- Boy Scout Rule: leave code better than found. In edited files, make small, safe, behavior-preserving improvements (no exported renames) and mention them in the change summary. Larger or cross-file cleanup goes in a separate change.
|
|
49
|
+
Design and style rules have one source: the `software-design` skill (SKILL.md plus references). Load the repository copy when present; otherwise install it (`pnpm dlx skills add adbayb/stack --skill software-design -g`); on conflict, the skill wins its defaults and this section carries only repo deltas (none today: add a repo-specific design rule here when one emerges). If the skill cannot be loaded, follow existing repository conventions and ask before structural changes.
|
|
92
50
|
|
|
93
51
|
## Security considerations
|
|
94
52
|
|
|
95
|
-
- Never commit secrets (tokens, credentials)
|
|
53
|
+
- Never commit secrets (tokens, credentials). Use environment variables and keep `.env*` git-ignored. Never log or print secrets; keep them out of error messages.
|
|
96
54
|
- Validate untrusted input (CLI args, env, files, network) once at the boundary; fail fast with actionable errors.
|
|
97
|
-
- Never interpolate untrusted input into shell commands
|
|
98
|
-
- Keep file writes scoped to the narrowest path set; destructive operations (`clean`)
|
|
99
|
-
-
|
|
55
|
+
- Never interpolate untrusted input into shell commands. Use parameterized exec helpers; never raw shell strings.
|
|
56
|
+
- Keep file writes scoped to the narrowest path set; destructive operations (`clean`) remove only git-ignored paths and generated cache directories, preserve `node_modules` and `.env*`, and refuse paths outside the working directory.
|
|
57
|
+
- Run `pnpm release:publish` (or any changeset publish) only through the documented release flow on a clean, CI-green tree; never publish ad hoc.
|
|
58
|
+
- Pin devDependencies exactly (`saveExact: true`, or `workspace:*` for local packages). Prefix `dependencies` and `peerDependencies` with a caret (or `workspace:^` for local dependencies; peers stay explicit). `stack check` verifies these rules. Review automated dependency updates (Renovate) before merging.
|
|
100
59
|
|
|
101
60
|
## PR instructions
|
|
102
61
|
|
|
103
|
-
-
|
|
62
|
+
- PR title follows Conventional Commits (checked by `conventional_commit.yml`); commit messages follow Conventional Commits (commitlint + `commit-msg` hook). CI must be green before merge.
|
|
104
63
|
- Before requesting review: `pnpm check` passes, valuable tests added, changelog entry included for non-internal changes (`pnpm release:changelog`).
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
|
|
3
3
|
A collection of examples to:
|
|
4
4
|
|
|
5
|
-
- Showcase the artifacts' usage (mainly for libraries)
|
|
6
|
-
- Source some scenarios for testing purposes (test cases)
|
|
5
|
+
- Showcase the artifacts' usage (mainly for libraries).
|
|
6
|
+
- Source some scenarios for testing purposes (test cases).
|