@next-friday/create-app 9.1.1 → 9.2.1
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 +5 -4
- package/dist/main.mjs +135 -96
- package/package.json +14 -10
- package/templates/base/.agents/hooks/agent-mcp-gate.sh +7 -2
- package/templates/base/.agents/hooks/agent-production-gate.sh +6 -1
- package/templates/base/.agents/hooks/agent-shell-gate.sh +1 -181
- package/templates/base/.agents/hooks/agent-test-edit-gate.sh +12 -3
- package/templates/base/.agents/review.md +2 -2
- package/templates/base/.agents/rules/no-suppression.md +1 -1
- package/templates/base/.agents/skill-evals/README.md +42 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/case.yaml +11 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/01-the-reply-explains-why-pnpm.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/02-no-file-in-the-project.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/03-no-commit-is-made-nothing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/05-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/graders/06-everything-the-reply-states-as.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/answers-question-without-writing/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/case.yaml +11 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/01-work-happens-on-the-plan-branch.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/02-the-plan-is-implemented-and-checked.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/03-the-branch-is-pushed.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/04-the-reply-lists-three-playbooks.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/05-no-approval-and-no-merge.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/06-the-reply-ends-at-the-code-owner.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/graders/07-the-plan-names-the-engineer.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-continues-from-an-accepted-plan/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/case.yaml +70 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/01-the-reply-asks-numbered-questions.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/02-no-plan-before-the-answers.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/03-no-question-depends-on-another.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/graders/04-no-file-or-commit.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-interviews-before-planning/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/case.yaml +67 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/01-the-plan-has-the-four-headings.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/02-no-source-file-or-commit.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/graders/03-the-reply-names-the-gate.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/build-plans-and-waits-for-the-engineer/prompt.md +10 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/case.yaml +7 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/01-no-deployment-is-started.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/02-the-reply-names-the-release-manager-gate.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/graders/03-the-reply-says-what-it-could-not-read.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/deploy-stops-at-the-production-gate/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/case.yaml +12 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/01-a-file-intentkebabcasenameintentmd-exists-with.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/02-the-reply-shows-the-draft.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/03-no-commit-is-made-nothing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/04-reply-lists-the-playbook-steps.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/06-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/graders/07-the-drafts-open-questions-section.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/drafts-intent-and-waits-for-yes/prompt.md +10 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/case.yaml +33 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/01-the-reply-shows-the-reproduction-output.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/02-the-reply-names-one-cause-with-evidence.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/03-the-plan-has-the-four-headings.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/04-no-file-or-commit.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/graders/05-the-reply-names-the-gate.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/fix-reproduces-finds-the-cause-and-waits-for-the-engineer/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/case.yaml +11 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/01-the-first-message-restates-the.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/02-the-reply-asks-numbered-questions.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/03-no-intentmd-is-written-before.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/04-reply-lists-the-playbook-steps.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/05-no-commit-is-made-nothing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/07-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/graders/08-no-question-in-the-round.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/interviews-before-writing-intent/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/case.yaml +14 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/01-the-intent-has-the-five-headings.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/02-problem-holds-the-error-and-where.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/03-unverified-cause-is-an-open-question.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/04-no-source-file-is-changed.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/05-a-branch-is-pushed-and-the-gate-is-named.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/graders/06-the-person-is-asked-no-question.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/maintain-writes-intent-from-a-finding/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/case.yaml +11 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/01-the-reply-follows-the-plan.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/02-the-reply-does-not-tell.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/03-no-file-is-created-or.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/graders/05-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/reported-problem-starts-the-plan/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/case.yaml +10 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/graders/01-no-file-is-written-and-nothing-is-committed.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/graders/02-the-reply-names-the-closest-row.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/graders/03-the-reply-is-short.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/request-that-fits-no-row/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/case.yaml +79 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/01-findings-have-pass-severity-and-location.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/02-no-approval-and-no-merge.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/03-the-reply-names-the-code-owner-gate.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/04-nothing-is-pushed-or-changed.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/graders/05-the-review-runs-in-a-fresh-context.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/review-reports-findings-and-never-merges/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/case.yaml +41 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/01-no-spec-is-written.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/02-no-commit-is-made.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/03-the-reply-says-not-merged.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/graders/05-the-reply-is-short.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/spec-for-unmerged-intent-stops/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/case.yaml +11 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/01-no-intentmd-or-any-other.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/02-no-commit-is-made-nothing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/04-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/graders/05-the-reply-says-that-work.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/statement-without-command-starts-no-work/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/case.yaml +41 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/fixture.md +1 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/01-the-spec-has-the-five-headings.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/02-policies-has-one-line-per-policy.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/03-no-policy-is-called-unwritten.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/04-a-branch-is-pushed-for-review.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/05-the-reply-names-the-gate.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/07-no-merge-is-made.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/graders/08-the-four-policies-are-read-before-writing.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc/writes-spec-from-merged-intent/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/case.yaml +10 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/01-the-reply-says-that-work.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/02-the-reply-gives-one-example.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/03-the-reply-lists-the-project.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/04-no-file-is-created-or.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/graders/06-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/explains-how-to-start-work/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/case.yaml +9 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/01-the-reply-says-that-this.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/02-the-reply-does-not-describe.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/03-no-file-is-created-or.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/graders/05-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/unknown-playbook-is-not-invented/prompt.md +8 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/case.yaml +10 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/01-the-reply-says-to-send.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/02-no-file-is-created-or.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/graders/04-every-path-in-the-reply.md +7 -0
- package/templates/base/.agents/skill-evals/sdlc-help/work-request-is-sent-to-sdlc/prompt.md +8 -0
- package/templates/base/.agents/skills/sdlc/SKILL.md +25 -21
- package/templates/base/.agents/skills/sdlc/playbooks/build.md +14 -9
- package/templates/base/.agents/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.agents/skills/sdlc/playbooks/design.md +3 -3
- package/templates/base/.agents/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.agents/skills/sdlc/playbooks/maintain.md +3 -4
- package/templates/base/.agents/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.agents/skills/sdlc/playbooks/plan.md +3 -3
- package/templates/base/.agents/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.agents/skills/sdlc-help/SKILL.md +4 -2
- package/templates/base/.claude/managed-settings.example.json +2 -2
- package/templates/base/.claude/settings.json +0 -13
- package/templates/base/.claude/skills/sdlc/SKILL.md +25 -21
- package/templates/base/.claude/skills/sdlc/playbooks/build.md +14 -9
- package/templates/base/.claude/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.claude/skills/sdlc/playbooks/design.md +3 -3
- package/templates/base/.claude/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.claude/skills/sdlc/playbooks/maintain.md +3 -4
- package/templates/base/.claude/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.claude/skills/sdlc/playbooks/plan.md +3 -3
- package/templates/base/.claude/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.claude/skills/sdlc-help/SKILL.md +4 -2
- package/templates/base/.cursor/hooks.json +0 -6
- package/templates/base/.cursor/rules/no-suppression.mdc +1 -1
- package/templates/base/.cursor/skills/sdlc/SKILL.md +25 -21
- package/templates/base/.cursor/skills/sdlc/playbooks/build.md +14 -9
- package/templates/base/.cursor/skills/sdlc/playbooks/deploy.md +2 -2
- package/templates/base/.cursor/skills/sdlc/playbooks/design.md +3 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/fix.md +23 -0
- package/templates/base/.cursor/skills/sdlc/playbooks/maintain.md +3 -4
- package/templates/base/.cursor/skills/sdlc/playbooks/open-pr.md +3 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/plan.md +3 -3
- package/templates/base/.cursor/skills/sdlc/playbooks/review.md +2 -2
- package/templates/base/.cursor/skills/sdlc-help/SKILL.md +4 -2
- package/templates/base/.github/ISSUE_TEMPLATE/documentation.yml +1 -1
- package/templates/base/.github/dependabot.yml +1 -10
- package/templates/base/.github/file-filters.yml +3 -6
- package/templates/base/.github/workflows/agent-evals.yml +46 -0
- package/templates/base/.github/workflows/claude-fix-loop.yml +45 -0
- package/templates/base/.github/workflows/control-band.yml +115 -0
- package/templates/base/.github/workflows/dependency-review.yml +1 -0
- package/templates/base/.github/workflows/deploy.yml +4 -2
- package/templates/base/.github/workflows/osv-scanner.yml +0 -5
- package/templates/base/.github/workflows/pr-triage.yml +1 -1
- package/templates/base/.github/workflows/secret-scan.yml +4 -1
- package/templates/base/.github/workflows/spec-on-intent-merge.yml +108 -0
- package/templates/base/.github/workflows/validate-pr-title.yml +3 -2
- package/templates/base/.husky/pre-push +1 -8
- package/templates/base/REVIEW.md +2 -2
- package/templates/base/SETUP.md +44 -8
- package/templates/base/bands.yaml +3 -3
- package/templates/base/intent/README.md +1 -0
- package/templates/base/knip.json +2 -1
- package/templates/base/pnpm-workspace.yaml +1 -22
- package/templates/base/runbooks/rollback-deploy.md +27 -0
- package/templates/base/scripts/check-agent-gates.ts +265 -226
- package/templates/base/scripts/check-bands.ts +665 -0
- package/templates/base/scripts/check-plan.ts +1047 -0
- package/templates/base/scripts/measure-loop.ts +293 -0
- package/templates/base/scripts/run-agent-evals.ts +1197 -0
- package/templates/base/scripts/sync-agent-rules.ts +0 -8
- package/templates/nestjs/.dependency-cruiser.json +1 -1
- package/templates/nestjs/.github/ISSUE_TEMPLATE/bug.yml +5 -5
- package/templates/nestjs/.github/workflows/ci.yml +58 -9
- package/templates/nestjs/.github/workflows/sast.yml +0 -5
- package/templates/nestjs/AGENTS.md +17 -13
- package/templates/nestjs/CONTRIBUTING.md +3 -2
- package/templates/nestjs/Dockerfile +4 -4
- package/templates/nestjs/README.md +4 -6
- package/templates/nestjs/_gitignore +0 -1
- package/templates/nestjs/package.json +21 -19
- package/templates/nestjs/src/app.controller.test.ts +1 -0
- package/templates/nestjs/src/health/health.controller.test.ts +1 -0
- package/templates/nestjs/src/port.test.ts +1 -0
- package/templates/nestjs/test/app.e2e.test.ts +1 -0
- package/templates/nestjs/tsconfig.build.json +2 -3
- package/templates/nestjs/tsconfig.json +1 -3
- package/templates/nestjs/vitest.config.ts +0 -1
- package/templates/nestjs/vitest.e2e.config.ts +0 -1
- package/templates/nextjs/.dependency-cruiser.json +1 -1
- package/templates/nextjs/.github/ISSUE_TEMPLATE/bug.yml +1 -1
- package/templates/nextjs/.github/workflows/ci.yml +58 -9
- package/templates/nextjs/.github/workflows/sast.yml +0 -5
- package/templates/nextjs/AGENTS.md +17 -13
- package/templates/nextjs/CONTRIBUTING.md +3 -2
- package/templates/nextjs/Dockerfile +2 -2
- package/templates/nextjs/README.md +5 -3
- package/templates/nextjs/app/layout.tsx +2 -2
- package/templates/nextjs/app/page.test.ts +1 -1
- package/templates/nextjs/app/page.tsx +1 -1
- package/templates/nextjs/package.json +15 -14
- package/templates/nextjs/test/home.e2e.test.ts +3 -7
- package/templates/nextjs/tsconfig.json +0 -3
- package/templates/nodejs/.dependency-cruiser.json +1 -1
- package/templates/nodejs/.github/ISSUE_TEMPLATE/bug.yml +5 -5
- package/templates/nodejs/.github/workflows/ci.yml +58 -9
- package/templates/nodejs/.github/workflows/sast.yml +0 -5
- package/templates/nodejs/AGENTS.md +17 -12
- package/templates/nodejs/CONTRIBUTING.md +4 -3
- package/templates/nodejs/Dockerfile +3 -3
- package/templates/nodejs/README.md +4 -4
- package/templates/nodejs/_gitignore +0 -1
- package/templates/nodejs/package.json +13 -11
- package/templates/nodejs/src/server.test.ts +23 -0
- package/templates/nodejs/src/server.ts +2 -2
- package/templates/nodejs/tsconfig.build.json +2 -3
- package/templates/nodejs/tsconfig.json +0 -4
- package/templates/payload/.agents/agents/verifier.md +1 -1
- package/templates/payload/.agents/review.md +21 -0
- package/templates/payload/.claude/agents/verifier.md +1 -1
- package/templates/payload/.cursor/agents/verifier.md +1 -1
- package/templates/payload/.dependency-cruiser.json +1 -1
- package/templates/payload/.github/ISSUE_TEMPLATE/bug.yml +1 -1
- package/templates/payload/.github/workflows/ci.yml +75 -10
- package/templates/payload/.github/workflows/sast.yml +0 -5
- package/templates/payload/AGENTS.md +22 -18
- package/templates/payload/CONTRIBUTING.md +4 -3
- package/templates/payload/Dockerfile +2 -2
- package/templates/payload/README.md +8 -4
- package/templates/payload/REVIEW.md +21 -0
- package/templates/payload/package.json +17 -15
- package/templates/payload/src/app/(frontend)/layout.tsx +2 -2
- package/templates/payload/src/app/(frontend)/page.test.ts +1 -1
- package/templates/payload/src/app/(frontend)/page.tsx +1 -1
- package/templates/payload/src/app/favicon.ico +0 -0
- package/templates/payload/src/app/health/route.test.ts +20 -2
- package/templates/payload/src/app/health/route.ts +9 -2
- package/templates/payload/src/collections/media.test.ts +36 -1
- package/templates/payload/src/collections/media.ts +1 -1
- package/templates/payload/src/payload.config.test.ts +15 -0
- package/templates/payload/test/home.e2e.test.ts +3 -7
- package/templates/payload/tsconfig.json +0 -1
- package/templates/turborepo/.agents/agents/verifier.md +1 -1
- package/templates/turborepo/.agents/review.md +21 -0
- package/templates/turborepo/.claude/agents/verifier.md +1 -1
- package/templates/turborepo/.cursor/agents/verifier.md +1 -1
- package/templates/turborepo/.dockerignore +9 -4
- package/templates/turborepo/.github/ISSUE_TEMPLATE/bug.yml +16 -1
- package/templates/turborepo/.github/dependabot.yml +1 -10
- package/templates/turborepo/.github/scripts/affected-apps.sh +5 -23
- package/templates/turborepo/.github/workflows/ci.yml +140 -103
- package/templates/turborepo/.github/workflows/deploy.yml +24 -23
- package/templates/turborepo/.github/workflows/sast.yml +0 -5
- package/templates/turborepo/AGENTS.md +22 -18
- package/templates/turborepo/CONTRIBUTING.md +6 -4
- package/templates/turborepo/README.md +8 -8
- package/templates/turborepo/REVIEW.md +21 -0
- package/templates/turborepo/apps/app/.dependency-cruiser.json +1 -1
- package/templates/turborepo/apps/app/AGENTS.md +9 -0
- package/templates/turborepo/apps/app/Dockerfile +2 -2
- package/templates/turborepo/apps/app/app/layout.tsx +2 -2
- package/templates/turborepo/apps/app/app/page.test.ts +1 -1
- package/templates/turborepo/apps/app/app/page.tsx +1 -1
- package/templates/turborepo/apps/app/package.json +9 -9
- package/templates/turborepo/apps/app/test/home.e2e.test.ts +3 -7
- package/templates/turborepo/apps/app/tsconfig.json +0 -3
- package/templates/turborepo/apps/backoffice/.dependency-cruiser.json +1 -1
- package/templates/turborepo/apps/backoffice/AGENTS.md +9 -0
- package/templates/turborepo/apps/backoffice/Dockerfile +2 -2
- package/templates/turborepo/apps/backoffice/package.json +12 -11
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/layout.tsx +2 -2
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/page.test.ts +1 -1
- package/templates/turborepo/apps/backoffice/src/app/(frontend)/page.tsx +1 -1
- package/templates/turborepo/apps/backoffice/src/app/favicon.ico +0 -0
- package/templates/turborepo/apps/backoffice/src/app/health/route.test.ts +20 -2
- package/templates/turborepo/apps/backoffice/src/app/health/route.ts +10 -2
- package/templates/turborepo/apps/backoffice/src/collections/media.test.ts +36 -1
- package/templates/turborepo/apps/backoffice/src/collections/media.ts +1 -1
- package/templates/turborepo/apps/backoffice/src/environment.test.ts +19 -0
- package/templates/turborepo/apps/backoffice/src/payload.config.test.ts +15 -0
- package/templates/turborepo/apps/backoffice/test/home.e2e.test.ts +3 -7
- package/templates/turborepo/apps/backoffice/tsconfig.json +0 -1
- package/templates/turborepo/knip.json +4 -2
- package/templates/turborepo/package.json +16 -16
- package/templates/turborepo/pnpm-workspace.yaml +1 -22
- package/templates/turborepo/turbo.json +32 -11
- package/templates/base/.agents/hooks/agent-suppression-gate.sh +0 -45
- package/templates/base/.claude/rules/agents-guide.md +0 -1
- package/templates/base/scripts/check-human-gates.ts +0 -383
- package/templates/base/scripts/check-intent.ts +0 -391
- package/templates/nextjs/knip.json +0 -4
- package/templates/payload/src/collections/users.test.ts +0 -10
- package/templates/turborepo/apps/backoffice/src/collections/users.test.ts +0 -10
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Every stack is built from [`templates/base`](templates/base), which holds the sh
|
|
|
29
29
|
## What every project includes
|
|
30
30
|
|
|
31
31
|
- **Code quality:** TypeScript, ESLint and Prettier with the Next Friday configs, Vitest with coverage, fast-check, Knip, dependency-cruiser, editorconfig-checker, and sort-package-json.
|
|
32
|
-
- **Git hooks:** Husky runs gitleaks and lint-staged before each commit, commitlint on each message, and `pnpm check` before each push.
|
|
32
|
+
- **Git hooks:** Husky runs gitleaks, `pnpm rules:check`, and lint-staged before each commit, commitlint on each message, and `pnpm check` before each push. After the first commit, commits to `main` are blocked, and local merges into `main` always are; pushes to `main` are blocked once it exists on the remote.
|
|
33
33
|
- **CI:** GitHub Actions run the static checks, tests with patch coverage, the build, end-to-end tests for Next.js and Payload, a container build and vulnerability scan, Semgrep, OSV-Scanner, CodeQL, dependency review, OpenSSF Scorecard, gitleaks, TruffleHog, zizmor, and pull request title validation. Dependabot keeps dependencies current.
|
|
34
34
|
- **Deployment:** a `Dockerfile` that runs as the `node` user, and a Railway workflow that promotes one commit through `dev`, `uat`, and `prod`.
|
|
35
35
|
- **Agent rules:** `CLAUDE.md` imports `AGENTS.md`. Shared rules live in `.agents/rules`, with hooks that enforce them in Claude Code and Cursor, and a Cursor Bugbot configuration.
|
|
@@ -37,9 +37,9 @@ Every stack is built from [`templates/base`](templates/base), which holds the sh
|
|
|
37
37
|
## Requirements
|
|
38
38
|
|
|
39
39
|
- Node.js 24.22.1, the version in the generated `.nvmrc`. The CLI itself runs on the range in [`package.json#engines`](package.json).
|
|
40
|
-
- pnpm: `
|
|
40
|
+
- pnpm, through Corepack: `corepack enable`. The generated project pins its pnpm version in `package.json#packageManager`.
|
|
41
41
|
- Git 2.28 or later.
|
|
42
|
-
- gitleaks 8.
|
|
42
|
+
- gitleaks 8.21 or later and jq, which the generated project's Git hooks need for the first commit.
|
|
43
43
|
|
|
44
44
|
## Options
|
|
45
45
|
|
|
@@ -60,6 +60,7 @@ The name must:
|
|
|
60
60
|
|
|
61
61
|
- use lowercase letters, digits, `.`, `_`, or `-`, and start with a letter or digit;
|
|
62
62
|
- not end in `.`, and not be `node_modules`, `favicon.ico`, or a name that Windows reserves, such as `con`;
|
|
63
|
+
- be at most 214 characters long;
|
|
63
64
|
- point to a directory that does not exist or is empty, apart from `.git`.
|
|
64
65
|
|
|
65
66
|
## What the CLI does
|
|
@@ -84,7 +85,7 @@ Then follow **After creating a project from this template** in the project's `RE
|
|
|
84
85
|
- **`pnpm install` fails:** the CLI keeps the project and exits with 1. Fix the printed error, such as a Node.js version outside the project's `package.json#engines`, then run `pnpm install` in the project.
|
|
85
86
|
- **`--skip-git`:** run `git init --initial-branch=main` before `pnpm install`, so the install can set up the Git hooks.
|
|
86
87
|
- **`--skip-install`:** the Git hooks are not set up until you run `pnpm install`.
|
|
87
|
-
- **Cancelling:** Ctrl+C
|
|
88
|
+
- **Cancelling:** Ctrl+C, SIGTERM, and SIGHUP exit with 130. During the copy, they also remove the partly copied project.
|
|
88
89
|
- **Payload:** set `DATABASE_URL` in `.env` to a PostgreSQL database before you start the app.
|
|
89
90
|
|
|
90
91
|
## Contributing
|
package/dist/main.mjs
CHANGED
|
@@ -1,68 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cancel, intro, isCancel, log, outro, select, text } from "@clack/prompts";
|
|
3
3
|
import { chmod, cp, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
4
|
-
import { existsSync, lstatSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { randomBytes } from "node:crypto";
|
|
8
8
|
import { spawnSync } from "node:child_process";
|
|
9
9
|
import { parseArgs } from "node:util";
|
|
10
|
-
// src/stacks.ts
|
|
11
|
-
const stacks = [
|
|
12
|
-
{
|
|
13
|
-
id: "nodejs",
|
|
14
|
-
label: "Node.js",
|
|
15
|
-
hint: "HTTP service without a framework",
|
|
16
|
-
requiresDatabase: false
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: "nestjs",
|
|
20
|
-
label: "NestJS",
|
|
21
|
-
hint: "HTTP API",
|
|
22
|
-
requiresDatabase: false
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: "nextjs",
|
|
26
|
-
label: "Next.js",
|
|
27
|
-
hint: "Web app",
|
|
28
|
-
requiresDatabase: false
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: "payload",
|
|
32
|
-
label: "Payload",
|
|
33
|
-
hint: "Headless CMS on Next.js and PostgreSQL",
|
|
34
|
-
requiresDatabase: true
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: "turborepo",
|
|
38
|
-
label: "Turborepo",
|
|
39
|
-
hint: "Next.js app and Payload backoffice",
|
|
40
|
-
requiresDatabase: true
|
|
41
|
-
}
|
|
42
|
-
];
|
|
43
|
-
const developmentScripts = {
|
|
44
|
-
nodejs: "start:dev",
|
|
45
|
-
nestjs: "start:dev",
|
|
46
|
-
nextjs: "dev",
|
|
47
|
-
payload: "dev",
|
|
48
|
-
turborepo: "dev"
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Check whether a string names a supported stack.
|
|
52
|
-
* @param value - Stack name from the command line.
|
|
53
|
-
* @returns Whether `value` is a supported stack id.
|
|
54
|
-
*/
|
|
55
|
-
function isStackId(value) {
|
|
56
|
-
return stacks.some((stack) => stack.id === value);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Name the package a stack's template declares, which the CLI replaces with the project name.
|
|
60
|
-
* @param stack - Supported stack id.
|
|
61
|
-
* @returns Package name in the template's `package.json`.
|
|
62
|
-
*/
|
|
63
|
-
function templateName(stack) {
|
|
64
|
-
return `next-friday-template-${stack}`;
|
|
65
|
-
}
|
|
66
10
|
// src/setup.ts
|
|
67
11
|
/**
|
|
68
12
|
* Write `.env` from `.env.example`, replacing the `PAYLOAD_SECRET` placeholder with a random secret.
|
|
@@ -90,7 +34,11 @@ function initGit(directory) {
|
|
|
90
34
|
return spawnSync("git", ["init", "--initial-branch=main"], {
|
|
91
35
|
cwd: directory,
|
|
92
36
|
env: environment,
|
|
93
|
-
stdio:
|
|
37
|
+
stdio: [
|
|
38
|
+
"ignore",
|
|
39
|
+
"ignore",
|
|
40
|
+
"inherit"
|
|
41
|
+
]
|
|
94
42
|
}).status === 0 ? "initialized" : "failed";
|
|
95
43
|
}
|
|
96
44
|
/**
|
|
@@ -118,6 +66,18 @@ function projectGitEnvironment() {
|
|
|
118
66
|
localVariables.delete("GIT_CONFIG_COUNT");
|
|
119
67
|
return Object.fromEntries(Object.entries(process.env).filter(([name]) => !localVariables.has(name)));
|
|
120
68
|
}
|
|
69
|
+
// src/project-files.ts
|
|
70
|
+
/**
|
|
71
|
+
* List the files of a project directory, leaving out a Git repository the directory already held.
|
|
72
|
+
* @param directory - Project directory.
|
|
73
|
+
* @returns Absolute paths of the files.
|
|
74
|
+
*/
|
|
75
|
+
async function listProjectFiles(directory) {
|
|
76
|
+
return (await readdir(directory, {
|
|
77
|
+
recursive: true,
|
|
78
|
+
withFileTypes: true
|
|
79
|
+
})).filter((entry) => entry.isFile()).map((entry) => path.join(entry.parentPath, entry.name)).filter((file) => path.relative(directory, file).split(path.sep)[0] !== ".git");
|
|
80
|
+
}
|
|
121
81
|
// src/project-name.ts
|
|
122
82
|
const packageNamePattern = /^[a-z0-9][a-z0-9._-]*$/;
|
|
123
83
|
const maxPackageNameLength = 214;
|
|
@@ -146,7 +106,7 @@ function validateProjectName(name, cwd) {
|
|
|
146
106
|
const target = path.resolve(cwd, name);
|
|
147
107
|
const stats = lstatSync(target, { throwIfNoEntry: false });
|
|
148
108
|
if (stats === void 0) return;
|
|
149
|
-
if (!stats.isDirectory()) return `${target} already exists and is not a directory.`;
|
|
109
|
+
if (!(stats.isSymbolicLink() ? statSync(target, { throwIfNoEntry: false }) : stats)?.isDirectory()) return `${target} already exists and is not a directory.`;
|
|
150
110
|
return ownEntries(target).length > 0 ? `${target} already exists and is not empty.` : void 0;
|
|
151
111
|
}
|
|
152
112
|
/**
|
|
@@ -157,8 +117,63 @@ function validateProjectName(name, cwd) {
|
|
|
157
117
|
function ownEntries(directory) {
|
|
158
118
|
return readdirSync(directory).filter((entry) => entry !== ".git");
|
|
159
119
|
}
|
|
120
|
+
// src/stacks.ts
|
|
121
|
+
const stacks = [
|
|
122
|
+
{
|
|
123
|
+
id: "nodejs",
|
|
124
|
+
label: "Node.js",
|
|
125
|
+
hint: "HTTP service without a framework",
|
|
126
|
+
developmentScript: "start:dev",
|
|
127
|
+
requiresDatabase: false
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "nestjs",
|
|
131
|
+
label: "NestJS",
|
|
132
|
+
hint: "HTTP API",
|
|
133
|
+
developmentScript: "start:dev",
|
|
134
|
+
requiresDatabase: false
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "nextjs",
|
|
138
|
+
label: "Next.js",
|
|
139
|
+
hint: "Web app",
|
|
140
|
+
developmentScript: "dev",
|
|
141
|
+
requiresDatabase: false
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "payload",
|
|
145
|
+
label: "Payload",
|
|
146
|
+
hint: "Headless CMS on Next.js and PostgreSQL",
|
|
147
|
+
developmentScript: "dev",
|
|
148
|
+
requiresDatabase: true
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "turborepo",
|
|
152
|
+
label: "Turborepo",
|
|
153
|
+
hint: "Next.js app and Payload backoffice",
|
|
154
|
+
developmentScript: "dev",
|
|
155
|
+
requiresDatabase: true
|
|
156
|
+
}
|
|
157
|
+
];
|
|
158
|
+
const stackById = Object.fromEntries(stacks.map((stack) => [stack.id, stack]));
|
|
159
|
+
/**
|
|
160
|
+
* Check whether a string names a supported stack.
|
|
161
|
+
* @param value - Stack name from the command line.
|
|
162
|
+
* @returns Whether `value` is a supported stack id.
|
|
163
|
+
*/
|
|
164
|
+
function isStackId(value) {
|
|
165
|
+
return stacks.some((stack) => stack.id === value);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Name the package a stack's template declares, which the CLI replaces with the project name.
|
|
169
|
+
* @param stack - Supported stack id.
|
|
170
|
+
* @returns Package name in the template's `package.json`.
|
|
171
|
+
*/
|
|
172
|
+
function templateName(stack) {
|
|
173
|
+
return `next-friday-template-${stack}`;
|
|
174
|
+
}
|
|
160
175
|
// src/arguments.ts
|
|
161
|
-
const usage = `Usage: pnpm create @next-friday/app [name] [--stack
|
|
176
|
+
const usage = `Usage: pnpm create @next-friday/app [name] [--stack ${stacks.map((stack) => stack.id).join("|")}]
|
|
162
177
|
|
|
163
178
|
name Project directory and package name, or . for the current directory. Asked when left out.
|
|
164
179
|
--stack Template stack. Asked when left out.
|
|
@@ -197,18 +212,19 @@ function parseCliArguments(cliArguments) {
|
|
|
197
212
|
}
|
|
198
213
|
}
|
|
199
214
|
});
|
|
200
|
-
if (positionals.length > 1) throw new Error(`Pass one project name, not ${positionals.length}: ${positionals.join(" ")}.`);
|
|
201
|
-
const [name] = positionals;
|
|
202
215
|
const { help, stack, version } = values;
|
|
203
|
-
|
|
216
|
+
const isInformational = help || version;
|
|
217
|
+
if (!isInformational && positionals.length > 1) throw new Error(`Pass one project name, not ${positionals.length}: ${positionals.join(" ")}.`);
|
|
218
|
+
const [name] = positionals;
|
|
219
|
+
if (!isInformational && stack !== void 0 && !isStackId(stack)) {
|
|
204
220
|
const ids = stacks.map((supported) => supported.id).join(", ");
|
|
205
221
|
throw new Error(`Unknown stack "${stack}". Use one of: ${ids}.`);
|
|
206
222
|
}
|
|
207
223
|
return {
|
|
208
224
|
help,
|
|
209
225
|
name,
|
|
210
|
-
stack,
|
|
211
226
|
version,
|
|
227
|
+
stack: stack !== void 0 && isStackId(stack) ? stack : void 0,
|
|
212
228
|
skipGit: values["skip-git"],
|
|
213
229
|
skipInstall: values["skip-install"]
|
|
214
230
|
};
|
|
@@ -219,23 +235,23 @@ function parseCliArguments(cliArguments) {
|
|
|
219
235
|
* @param directory - Project directory.
|
|
220
236
|
* @param from - Template name to replace.
|
|
221
237
|
* @param to - Project name.
|
|
222
|
-
* @returns Paths of the files that changed.
|
|
223
238
|
*/
|
|
224
239
|
async function renameProject(directory, from, to) {
|
|
225
|
-
const files =
|
|
226
|
-
|
|
227
|
-
withFileTypes: true
|
|
228
|
-
})).filter((entry) => entry.isFile()).map((entry) => path.join(entry.parentPath, entry.name));
|
|
229
|
-
return (await Promise.all(files.map(async (file) => {
|
|
240
|
+
const files = await listProjectFiles(directory);
|
|
241
|
+
await Promise.all(files.map(async (file) => {
|
|
230
242
|
const content = await readFile(file);
|
|
231
243
|
const text = content.toString("utf8");
|
|
232
|
-
if (content.includes(0) || !content.includes(from) || !Buffer.from(text, "utf8").equals(content)) return
|
|
244
|
+
if (content.includes(0) || !content.includes(from) || !Buffer.from(text, "utf8").equals(content)) return;
|
|
233
245
|
await writeFile(file, text.replaceAll(from, () => to));
|
|
234
|
-
|
|
235
|
-
}))).flat();
|
|
246
|
+
}));
|
|
236
247
|
}
|
|
237
248
|
// src/create.ts
|
|
238
249
|
const templatesDirectory = fileURLToPath(new URL("../templates", import.meta.url));
|
|
250
|
+
const interruptSignals = [
|
|
251
|
+
"SIGINT",
|
|
252
|
+
"SIGTERM",
|
|
253
|
+
"SIGHUP"
|
|
254
|
+
];
|
|
239
255
|
var CancelledError = class extends Error {
|
|
240
256
|
message = "Cancelled.";
|
|
241
257
|
};
|
|
@@ -244,8 +260,8 @@ var CancelledError = class extends Error {
|
|
|
244
260
|
* @param cliArguments - Arguments after the executable and script path.
|
|
245
261
|
*/
|
|
246
262
|
async function create(cliArguments) {
|
|
247
|
-
const cwd = process.cwd();
|
|
248
263
|
try {
|
|
264
|
+
const cwd = process.cwd();
|
|
249
265
|
const options = parseCliArguments(cliArguments);
|
|
250
266
|
if (options.help) {
|
|
251
267
|
process.stdout.write(`${usage}\n`);
|
|
@@ -255,23 +271,28 @@ async function create(cliArguments) {
|
|
|
255
271
|
process.stdout.write(`${packageVersion()}\n`);
|
|
256
272
|
return;
|
|
257
273
|
}
|
|
258
|
-
intro("Create a Next Friday project");
|
|
259
274
|
const { name, stack } = await resolveProject(options, cwd);
|
|
260
275
|
const manifestName = packageName(name, cwd);
|
|
261
|
-
const
|
|
276
|
+
const target = path.resolve(cwd, name);
|
|
277
|
+
const directory = existsSync(target) ? realpathSync(target) : target;
|
|
262
278
|
log.step(`Copying the ${stack} template.`);
|
|
263
279
|
await writeProject(stack, directory, manifestName);
|
|
264
280
|
log.success(`Created ${manifestName} from the ${stack} template.`);
|
|
265
281
|
const { git, isInstalled } = setUpProject(directory, options);
|
|
266
282
|
outro(nextSteps(name, stack, git, isInstalled));
|
|
267
283
|
} catch (error) {
|
|
268
|
-
|
|
269
|
-
|
|
284
|
+
if (error instanceof CancelledError) {
|
|
285
|
+
cancel(error.message);
|
|
286
|
+
process.exitCode = 130;
|
|
287
|
+
} else {
|
|
288
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
289
|
+
process.exitCode = 1;
|
|
290
|
+
}
|
|
270
291
|
}
|
|
271
292
|
}
|
|
272
293
|
/**
|
|
273
|
-
* Copy, rename, and write `.env`, removing what was written when a step fails or Ctrl+C arrives.
|
|
274
|
-
*
|
|
294
|
+
* Copy, rename, and write `.env`, removing what was written when a step fails or Ctrl+C, SIGTERM, or SIGHUP arrives.
|
|
295
|
+
* An interrupt waits for the running step, so the removal does not race the copy.
|
|
275
296
|
* @param stack - Supported stack id.
|
|
276
297
|
* @param directory - Project directory.
|
|
277
298
|
* @param manifestName - Package name for `package.json`.
|
|
@@ -282,7 +303,7 @@ async function writeProject(stack, directory, manifestName) {
|
|
|
282
303
|
const interrupt = () => {
|
|
283
304
|
isInterrupted = true;
|
|
284
305
|
};
|
|
285
|
-
process.on(
|
|
306
|
+
for (const signal of interruptSignals) process.on(signal, interrupt);
|
|
286
307
|
try {
|
|
287
308
|
await copyTemplate(stack, directory);
|
|
288
309
|
await renameProject(directory, templateName(stack), manifestName);
|
|
@@ -292,7 +313,7 @@ async function writeProject(stack, directory, manifestName) {
|
|
|
292
313
|
removeProject(directory, kept);
|
|
293
314
|
throw isInterrupted ? new CancelledError() : error;
|
|
294
315
|
} finally {
|
|
295
|
-
process.off(
|
|
316
|
+
for (const signal of interruptSignals) process.off(signal, interrupt);
|
|
296
317
|
}
|
|
297
318
|
}
|
|
298
319
|
/**
|
|
@@ -303,16 +324,28 @@ async function writeProject(stack, directory, manifestName) {
|
|
|
303
324
|
*/
|
|
304
325
|
async function resolveProject(options, cwd) {
|
|
305
326
|
if (!process.stdin.isTTY && (options.name === void 0 || options.stack === void 0)) throw new Error(`Pass the project name and --stack when not running in a terminal.\n${usage}`);
|
|
327
|
+
if (options.name !== void 0) assertUsableName(options.name, cwd);
|
|
328
|
+
intro("Create a Next Friday project");
|
|
306
329
|
const name = options.name ?? await askName(cwd);
|
|
307
|
-
const
|
|
308
|
-
|
|
330
|
+
const stack = options.stack ?? await askStack();
|
|
331
|
+
assertUsableName(name, cwd);
|
|
309
332
|
return {
|
|
310
333
|
name,
|
|
311
|
-
stack
|
|
334
|
+
stack
|
|
312
335
|
};
|
|
313
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Throw when the project name is unusable or its directory is taken, as a prompt may have left a
|
|
339
|
+
* directory in place since the name was checked.
|
|
340
|
+
* @param name - Project name, or `.` for the current directory.
|
|
341
|
+
* @param cwd - Directory the project is created in.
|
|
342
|
+
*/
|
|
343
|
+
function assertUsableName(name, cwd) {
|
|
344
|
+
const nameError = validateProjectName(name, cwd);
|
|
345
|
+
if (nameError !== void 0) throw new Error(nameError);
|
|
346
|
+
}
|
|
314
347
|
const gitWarnings = {
|
|
315
|
-
failed: "git init failed.
|
|
348
|
+
failed: "git init failed. Check that Git 2.28 or later is installed and the directory is writable, then run the steps below.",
|
|
316
349
|
parent: "Skipped git init inside an existing Git repository. The Git hooks install only at a repository root."
|
|
317
350
|
};
|
|
318
351
|
/**
|
|
@@ -322,7 +355,7 @@ const gitWarnings = {
|
|
|
322
355
|
* @returns The `git init` result, or `undefined` when skipped, and whether the install succeeded.
|
|
323
356
|
*/
|
|
324
357
|
function setUpProject(directory, options) {
|
|
325
|
-
const git = options.skipGit ?
|
|
358
|
+
const git = options.skipGit ? existingGit(directory) : initGit(directory);
|
|
326
359
|
const warning = git === void 0 ? void 0 : gitWarnings[git];
|
|
327
360
|
if (warning !== void 0) log.warn(warning);
|
|
328
361
|
if (options.skipInstall) return {
|
|
@@ -332,7 +365,7 @@ function setUpProject(directory, options) {
|
|
|
332
365
|
log.step("Installing dependencies with pnpm.");
|
|
333
366
|
const isInstalled = didInstallDependencies(directory);
|
|
334
367
|
if (!isInstalled) {
|
|
335
|
-
log.error("pnpm install failed. Fix the error above, then run the steps below. The project needs pnpm and the Node.js version in .
|
|
368
|
+
log.error("pnpm install failed. Fix the error above, then run the steps below. The project needs pnpm and the Node.js version in package.json#engines.");
|
|
336
369
|
process.exitCode = 1;
|
|
337
370
|
}
|
|
338
371
|
return {
|
|
@@ -341,12 +374,21 @@ function setUpProject(directory, options) {
|
|
|
341
374
|
};
|
|
342
375
|
}
|
|
343
376
|
/**
|
|
377
|
+
* Report a repository that skipping `git init` leaves in place.
|
|
378
|
+
* @param directory - Project directory.
|
|
379
|
+
* @returns `existing` when the directory holds `.git`, otherwise `undefined`.
|
|
380
|
+
*/
|
|
381
|
+
function existingGit(directory) {
|
|
382
|
+
return existsSync(path.join(directory, ".git")) ? "existing" : void 0;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
344
385
|
* Copy the shared template files, then the stack's files, into the project directory.
|
|
345
386
|
* npm leaves `.gitignore` out of published packages, so the template keeps it as `_gitignore`.
|
|
346
387
|
* @param stack - Supported stack id.
|
|
347
388
|
* @param directory - Project directory.
|
|
348
389
|
*/
|
|
349
390
|
async function copyTemplate(stack, directory) {
|
|
391
|
+
for (const template of ["base", stack]) if (!existsSync(path.join(templatesDirectory, template))) throw new Error(`The ${template} template is missing from ${templatesDirectory}. Reinstall @next-friday/create-app.`);
|
|
350
392
|
await cp(path.join(templatesDirectory, "base"), directory, { recursive: true });
|
|
351
393
|
await cp(path.join(templatesDirectory, stack), directory, { recursive: true });
|
|
352
394
|
await rename(path.join(directory, "_gitignore"), path.join(directory, ".gitignore"));
|
|
@@ -358,12 +400,8 @@ async function copyTemplate(stack, directory) {
|
|
|
358
400
|
* @param directory - Project directory.
|
|
359
401
|
*/
|
|
360
402
|
async function markScriptsExecutable(directory) {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
withFileTypes: true
|
|
364
|
-
});
|
|
365
|
-
await Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => {
|
|
366
|
-
const file = path.join(entry.parentPath, entry.name);
|
|
403
|
+
const files = await listProjectFiles(directory);
|
|
404
|
+
await Promise.all(files.map(async (file) => {
|
|
367
405
|
if ((await readFile(file)).subarray(0, 2).toString() === "#!") await chmod(file, 493);
|
|
368
406
|
}));
|
|
369
407
|
}
|
|
@@ -388,14 +426,15 @@ function removeProject(directory, kept) {
|
|
|
388
426
|
* @returns The next steps message.
|
|
389
427
|
*/
|
|
390
428
|
function nextSteps(name, stack, git, isInstalled) {
|
|
429
|
+
const { developmentScript, requiresDatabase } = stackById[stack];
|
|
391
430
|
const hasGit = git !== void 0 && git !== "failed";
|
|
392
431
|
const commands = [
|
|
393
432
|
name === "." ? [] : [`cd ${name}`],
|
|
394
433
|
hasGit ? [] : ["git init --initial-branch=main"],
|
|
395
434
|
hasGit && isInstalled ? [] : ["pnpm install"],
|
|
396
|
-
[`pnpm ${
|
|
435
|
+
[`pnpm ${developmentScript}`]
|
|
397
436
|
].flat();
|
|
398
|
-
const database =
|
|
437
|
+
const database = requiresDatabase ? `Set DATABASE_URL in .env to your PostgreSQL database before pnpm ${developmentScript}.\n` : "";
|
|
399
438
|
const readme = name === "." ? "README.md" : `${name}/README.md`;
|
|
400
439
|
return `Next steps:\n${commands.map((command) => ` ${command}`).join("\n")}\n${database}Before the first commit, follow "After creating a project from this template" in ${readme}.`;
|
|
401
440
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next-friday/create-app",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.1",
|
|
4
4
|
"description": "Create a Next Friday project from a template.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@next-friday/eslint-config-friday": "5.1.0",
|
|
41
41
|
"@next-friday/prettier-config-friday": "4.0.0",
|
|
42
42
|
"@types/node": "26.6.6",
|
|
43
|
-
"@vitest/coverage-v8": "5.0
|
|
43
|
+
"@vitest/coverage-v8": "5.1.0",
|
|
44
44
|
"editorconfig-checker": "7.0.0",
|
|
45
45
|
"eslint": "10.14.0",
|
|
46
46
|
"fast-check": "4.10.2",
|
|
@@ -52,9 +52,10 @@
|
|
|
52
52
|
"prettier": "3.9.10",
|
|
53
53
|
"publint": "0.3.25",
|
|
54
54
|
"sort-package-json": "4.1.0",
|
|
55
|
-
"tsdown": "0.23.
|
|
56
|
-
"typescript": "
|
|
57
|
-
"vitest": "5.0
|
|
55
|
+
"tsdown": "0.23.5",
|
|
56
|
+
"typescript": "7.0.2",
|
|
57
|
+
"vitest": "5.1.0",
|
|
58
|
+
"yaml": "2.9.1"
|
|
58
59
|
},
|
|
59
60
|
"engines": {
|
|
60
61
|
"node": ">=24.15.0"
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
},
|
|
66
67
|
"scripts": {
|
|
67
68
|
"audit": "pnpm audit",
|
|
69
|
+
"bands:check": "node plugins/sdlc/source/scripts/check-bands.ts",
|
|
68
70
|
"build": "tsdown",
|
|
69
71
|
"changeset": "changeset",
|
|
70
72
|
"changeset:status": "changeset status --since main",
|
|
@@ -72,24 +74,26 @@
|
|
|
72
74
|
"check:publint": "publint",
|
|
73
75
|
"clean": "rm -rf dist coverage *.tsbuildinfo .eslintcache node_modules/.cache",
|
|
74
76
|
"dev": "tsdown --watch",
|
|
77
|
+
"evals:run": "claude plugin eval plugins/sdlc/adapters/claude-code --scaffold --tag no-shell --allow-tools Write Edit",
|
|
78
|
+
"evals:run:write": "claude plugin eval plugins/sdlc/adapters/claude-code --scaffold --tag needs-write --allow-tools Write Edit Bash",
|
|
75
79
|
"format": "prettier --write --cache .",
|
|
76
80
|
"format:check": "prettier --check --cache . && editorconfig-checker",
|
|
77
81
|
"gates:check": "node scripts/check-agent-gates.ts",
|
|
78
|
-
"human-gates:check": "node scripts/check-human-gates.ts",
|
|
79
|
-
"intent:check": "node scripts/check-intent.ts",
|
|
80
82
|
"knip": "knip",
|
|
81
83
|
"lint": "eslint . --fix --cache --max-warnings=0 --no-inline-config",
|
|
82
84
|
"lint:check": "eslint . --cache --max-warnings=0 --no-inline-config",
|
|
83
|
-
"loop:measure": "node scripts/measure-loop.ts",
|
|
85
|
+
"loop:measure": "node plugins/sdlc/source/scripts/measure-loop.ts",
|
|
86
|
+
"plan:check": "node plugins/sdlc/source/scripts/check-plan.ts",
|
|
87
|
+
"plugin:check": "node scripts/sync-plugin.ts --check",
|
|
88
|
+
"plugin:sync": "node scripts/sync-plugin.ts",
|
|
84
89
|
"rules:check": "node scripts/sync-agent-rules.ts --check",
|
|
85
90
|
"rules:sync": "node scripts/sync-agent-rules.ts",
|
|
86
91
|
"sort-package-json": "sort-package-json package.json",
|
|
87
92
|
"sort-package-json:check": "sort-package-json --check package.json",
|
|
88
|
-
"suppressions:check": "bash .agents/hooks/agent-suppression-gate.sh --repo",
|
|
89
93
|
"test": "vitest run",
|
|
90
94
|
"test:coverage": "vitest run --coverage",
|
|
91
95
|
"test:watch": "vitest",
|
|
92
96
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
93
|
-
"verify": "pnpm
|
|
97
|
+
"verify": "pnpm rules:check && pnpm plugin:check && pnpm gates:check && pnpm plan:check && pnpm bands:check && pnpm changesets:check && pnpm lint:check && pnpm format:check && pnpm sort-package-json:check && pnpm typecheck && pnpm test:coverage && pnpm knip && pnpm build && pnpm check:publint"
|
|
94
98
|
}
|
|
95
99
|
}
|
|
@@ -16,7 +16,12 @@ trap 'block ".agents/hooks/agent-mcp-gate.sh failed. Stop and report this error
|
|
|
16
16
|
command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
|
|
17
17
|
input=$(cat)
|
|
18
18
|
tool=$(jq -r '.tool_name // empty' <<< "$input")
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
human_gate="A code owner approves a pull request and a person merges it. Do not approve or merge; report that the pull request waits on that review."
|
|
20
|
+
if [[ $tool =~ (merge_pull_request|enable_pr_auto_merge)$ ]]; then
|
|
21
|
+
block "$human_gate"
|
|
22
|
+
fi
|
|
23
|
+
if [[ $tool =~ pull_request_review_write$ ]]; then
|
|
24
|
+
event=$(jq -r '(.tool_input | if type == "string" then (fromjson? // {}) else . end | objects | .event) // empty' <<< "$input" | tr '[:upper:]' '[:lower:]')
|
|
25
|
+
[[ $event == approve ]] && block "$human_gate"
|
|
21
26
|
fi
|
|
22
27
|
allow
|
|
@@ -15,7 +15,12 @@ block() {
|
|
|
15
15
|
trap 'block ".agents/hooks/agent-production-gate.sh failed. Stop and report this error to the user; agents may not edit the gate."' ERR
|
|
16
16
|
command -v jq > /dev/null || block "jq is required to read the hook input. Install it: https://jqlang.org/download/"
|
|
17
17
|
command=$(jq -r '.command // .tool_input.command // empty')
|
|
18
|
-
[[ -n $command
|
|
18
|
+
[[ -n $command ]] || allow
|
|
19
|
+
approval=$(tr '[:upper:]' '[:lower:]' <<< "${RELEASE_APPROVAL:-}")
|
|
20
|
+
case $approval in
|
|
21
|
+
"" | 0 | false | no) ;;
|
|
22
|
+
*) allow ;;
|
|
23
|
+
esac
|
|
19
24
|
|
|
20
25
|
release_gate="A release manager runs prod deploys, commands against prod, and release tag pushes. Do not run this; report what is ready to promote, such as the commit SHA. If a gated command appears only inside a message, write the text to a file and pass it with git commit -F <file> or gh pr create --body-file <file>."
|
|
21
26
|
|