@joshuafolkken/kit 1.1387.0 → 1.1453.0
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/.claude/skills/epic-commands/SKILL.md +1 -1
- package/.claude/skills/epic-commands/epic-bundle.md +5 -0
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +5 -3
- package/.claude/skills/workflow-commands/backlogrun-park.md +2 -0
- package/.claude/skills/workflow-commands/backlogrun-steps.md +13 -10
- package/.claude/skills/workflow-commands/backlogrun.md +3 -2
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
- package/.claude/skills/workflow-commands/halfrun.md +1 -1
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/kickoff.md +12 -13
- package/.claude/skills/workflow-commands/observation-filing.md +1 -1
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/.claude/skills/workflow-commands/prerequisite.md +6 -0
- package/.claude/skills/workflow-commands/retrospective.md +5 -5
- package/.claude/skills/workflow-commands/split-assessment.md +2 -2
- package/README.md +27 -4
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-2YSW3CXH.js → chunk-3C4XRYG6.js} +35 -11
- package/dist/hooks/{chunk-GJ7JPIHN.js → chunk-5C7L6FRC.js} +2704 -190
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-STDSEPWU.js → chunk-D3NJXN5M.js} +4 -4
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-LMWBRJIP.js → chunk-EF5GQ6WS.js} +3 -3
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-IYVOLN6A.js → chunk-LBFYEOXJ.js} +3 -3
- package/dist/hooks/{chunk-SL4GQ2XP.js → chunk-MBJYQPEK.js} +106 -23
- package/dist/hooks/{chunk-WNFJLBXI.js → chunk-NO4AALP5.js} +3 -3
- package/dist/hooks/{chunk-J3KSS7II.js → chunk-OLMO72ON.js} +1 -1
- package/dist/hooks/{chunk-DCY2GWHC.js → chunk-SHYUQOIS.js} +5 -5
- package/dist/hooks/{chunk-EAQEGME7.js → chunk-UT7YC3N2.js} +1 -1
- package/dist/hooks/{chunk-EBSHN25W.js → chunk-W5ZKGTSY.js} +4 -4
- package/dist/hooks/{chunk-GNDJ7NJ7.js → chunk-Y3GKIKGW.js} +209 -56
- package/dist/hooks/{chunk-U32VSRNG.js → chunk-Z4XI7CZS.js} +89 -34
- package/dist/hooks/codex-hook-adapter.js +15 -15
- package/dist/hooks/duplicate-read-guard.js +8 -8
- package/dist/hooks/format-edited.js +5 -5
- package/dist/hooks/inputs.json +261 -201
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +69 -16
- package/dist/hooks/stop-guard.js +13 -38
- package/dist/josh.js +95 -19
- package/package.json +4 -3
- package/prompts/collaboration-workflow/issue-template.md +10 -8
- package/prompts/review.md +30 -4
- package/scripts/agent/claude-agent-argv.ts +13 -1
- package/scripts/auth/auth-setup-cli.ts +101 -0
- package/scripts/auth/claude-token-output.ts +78 -0
- package/scripts/auth/claude-token.ts +68 -0
- package/scripts/auto-ok/auto-ok-judge-cli.ts +125 -0
- package/scripts/auto-ok/auto-ok-judge-read.ts +95 -0
- package/scripts/auto-ok/auto-ok-judge-write.ts +109 -0
- package/scripts/auto-ok/auto-ok-judge.ts +67 -0
- package/scripts/backlog/backlog-drive-cli.ts +14 -0
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +3 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +7 -2
- package/scripts/backlog/backlog-drive-named.ts +4 -2
- package/scripts/backlog/backlog-drive-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +38 -11
- package/scripts/backlog/backlog-overlap.ts +22 -5
- package/scripts/backlog/backlog-ready.ts +1 -3
- package/scripts/backlog/backlogrun-args.ts +13 -2
- package/scripts/backlog/backlogrun-cli.ts +91 -19
- package/scripts/backlog/backlogrun-launch.ts +24 -0
- package/scripts/backlog/backlogrun-replace.ts +166 -0
- package/scripts/backlog/backlogrun-stop.ts +178 -0
- package/scripts/behavior/behavior-cli.ts +6 -1
- package/scripts/doctor/doctor-io.ts +60 -9
- package/scripts/doctor/doctor.ts +20 -1
- package/scripts/document/document-byte-budget.ts +3 -3
- package/scripts/epic/epic-bundle-cli.ts +8 -8
- package/scripts/epic/epic-bundle-referenced.ts +2 -5
- package/scripts/epic/epic-bundle.ts +39 -4
- package/scripts/exports/unused-members-cli.ts +2 -1
- package/scripts/followup/followup-checks-event.ts +82 -0
- package/scripts/followup/git-followup-finish.ts +16 -2
- package/scripts/followup/git-followup-workflow.ts +5 -2
- package/scripts/followup/git-pr-followup-checks.ts +18 -4
- package/scripts/followup/git-pr-followup-wrapup.ts +5 -0
- package/scripts/followup/git-pr-followup.ts +4 -1
- package/scripts/fresh-machine/fresh-machine-cli.ts +204 -0
- package/scripts/fresh-machine/fresh-machine-docker.ts +65 -0
- package/scripts/fresh-machine/fresh-machine-image.ts +62 -0
- package/scripts/fresh-machine/fresh-machine-log.ts +61 -0
- package/scripts/fresh-machine/fresh-machine-patterns.ts +175 -0
- package/scripts/fresh-machine/fresh-machine-run.ts +215 -0
- package/scripts/fresh-machine/fresh-machine-script.ts +45 -0
- package/scripts/fresh-machine/fresh-machine-spec.ts +57 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +34 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +31 -0
- package/scripts/gate/gate-report.ts +70 -15
- package/scripts/gate/project-checks.ts +18 -4
- package/scripts/gate/scoped-green.ts +2 -1
- package/scripts/gate/verification-gate.ts +4 -3
- package/scripts/gh/gh-author-association.ts +14 -0
- package/scripts/gh/git-gh-conditional.ts +66 -0
- package/scripts/gh/git-gh-exec.ts +1 -1
- package/scripts/gh/git-gh-issue-read.ts +6 -3
- package/scripts/gh/git-gh-issue-rest.ts +1 -0
- package/scripts/gh/git-gh-issue-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +13 -1
- package/scripts/gh/git-pr-checks-eval.ts +13 -0
- package/scripts/gh/git-pr-checks-parse.ts +8 -1
- package/scripts/gh/git-pr-checks-watch.ts +11 -4
- package/scripts/gh/git-pr-checks.ts +36 -2
- package/scripts/git/git-command.ts +16 -4
- package/scripts/git/git-schemas.ts +1 -0
- package/scripts/git/git-spawn-sync.ts +1 -1
- package/scripts/hooks/pretool-guard.ts +3 -1
- package/scripts/init/hook-command-rewrite.ts +31 -0
- package/scripts/init/init-actions.ts +7 -4
- package/scripts/init/init-ai-copy.ts +34 -11
- package/scripts/init/init-basic.ts +9 -4
- package/scripts/init/init-bootstrap.ts +6 -2
- package/scripts/init/init-dry-run-report.ts +150 -0
- package/scripts/init/init-dry-run.ts +189 -0
- package/scripts/init/init-install.ts +8 -1
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +62 -42
- package/scripts/init/kit-setup-state.ts +11 -17
- package/scripts/init/own-formatter.ts +21 -0
- package/scripts/init/project-profile.ts +60 -12
- package/scripts/init/start-dry-run.ts +70 -0
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +23 -3
- package/scripts/init/start-setup-pr.ts +13 -24
- package/scripts/init/start-steps.ts +62 -1
- package/scripts/init/start.ts +38 -17
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/init/working-tree.ts +116 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-auto-ok.ts +68 -99
- package/scripts/issue/issue-file-cli.ts +17 -17
- package/scripts/issue/issue-file.ts +1 -6
- package/scripts/issue/issue-labels.ts +46 -10
- package/scripts/issue/issue-release-cli.ts +69 -23
- package/scripts/issue/issue-release.ts +3 -1
- package/scripts/issue/issue-state-cli.ts +14 -4
- package/scripts/issue/issue-watch-diff.ts +152 -0
- package/scripts/issue/issue-watch-gate.ts +81 -0
- package/scripts/issue/issue-watch-hook.ts +54 -0
- package/scripts/issue/issue-watch.ts +240 -0
- package/scripts/issue/measure-rerun-cli.ts +3 -4
- package/scripts/josh/josh-command-map.ts +4 -0
- package/scripts/josh/josh-commands-ai.ts +11 -0
- package/scripts/josh/josh-commands-backlog.ts +2 -2
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-issue.ts +10 -3
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +26 -2
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/josh/process-identity.ts +32 -6
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- package/scripts/lane/lane-broker-server.ts +69 -0
- package/scripts/lane/lane-cache.ts +37 -7
- package/scripts/lane/lane-capacity.ts +8 -6
- package/scripts/lane/lane-child-invocation.ts +1 -0
- package/scripts/lane/lane-cli.ts +15 -1
- package/scripts/lane/lane-close.ts +15 -6
- package/scripts/lane/lane-dispatch.ts +36 -10
- package/scripts/lane/lane-environment.ts +1 -1
- package/scripts/lane/lane-launch-cli.ts +13 -4
- package/scripts/lane/lane-ledger.ts +21 -3
- package/scripts/lane/lane-measure-cli.ts +10 -2
- package/scripts/lane/lane-open.ts +9 -1
- package/scripts/lane/lane-preserve.ts +49 -0
- package/scripts/lane/lane-reap.ts +20 -5
- package/scripts/lane/lane-registry.ts +6 -0
- package/scripts/lane/lane-restart-cli.ts +51 -0
- package/scripts/lane/lane-restart.ts +175 -0
- package/scripts/lane/lane-session-broker.ts +363 -0
- package/scripts/lane/lane-spent.ts +195 -0
- package/scripts/lane/lane-stage-stats.ts +27 -3
- package/scripts/lane/lane-stop-stats.ts +96 -0
- package/scripts/lane/openai-lane-supervisor.ts +2 -2
- package/scripts/lane/process-tools.ts +31 -0
- package/scripts/lib/buffered-process.ts +36 -2
- package/scripts/lib/pnpm-reach.ts +27 -0
- package/scripts/lib/status-icons.ts +5 -1
- package/scripts/lint/lint-parallel.ts +19 -2
- package/scripts/observations/observation-ledger-home.ts +1 -0
- package/scripts/observations/observation-ledger-migrate.ts +3 -15
- package/scripts/release/release-issue-close.ts +133 -0
- package/scripts/release/release-publish.ts +12 -1
- package/scripts/repo/repository-labels.ts +11 -1
- package/scripts/review/comment-ranges.ts +199 -0
- package/scripts/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +24 -7
- package/scripts/review/review-code-tree.ts +119 -0
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- package/scripts/review/review-round2-cli.ts +16 -5
- package/scripts/review/review-round2.ts +30 -10
- package/scripts/review/review-stamps.ts +22 -1
- package/scripts/rules/implementation-cut.ts +3 -3
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/run/board/run-board-checks.ts +42 -0
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-every.ts +3 -1
- package/scripts/run/board/run-board-fit.ts +16 -5
- package/scripts/run/board/run-board-fresh.ts +64 -0
- package/scripts/run/board/run-board-header.ts +129 -53
- package/scripts/run/board/run-board-labels.ts +47 -4
- package/scripts/run/board/run-board-lane-event.ts +66 -0
- package/scripts/run/board/run-board-layout.ts +57 -9
- package/scripts/run/board/run-board-now.ts +234 -0
- package/scripts/run/board/run-board-operation-read.ts +75 -0
- package/scripts/run/board/run-board-operation.ts +150 -0
- package/scripts/run/board/run-board-phase.ts +26 -1
- package/scripts/run/board/run-board-plan.ts +4 -6
- package/scripts/run/board/run-board-read.ts +49 -3
- package/scripts/run/board/run-board-reason.ts +124 -0
- package/scripts/run/board/run-board-render-checks.ts +55 -0
- package/scripts/run/board/run-board-render-legend.ts +12 -3
- package/scripts/run/board/run-board-render-slot.ts +2 -2
- package/scripts/run/board/run-board-render-tail.ts +128 -0
- package/scripts/run/board/run-board-render-thanks.ts +50 -0
- package/scripts/run/board/run-board-render-title.ts +58 -0
- package/scripts/run/board/run-board-render.ts +96 -80
- package/scripts/run/board/run-board-row-yield.ts +32 -0
- package/scripts/run/board/run-board-setup.ts +61 -13
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +66 -7
- package/scripts/run/board/run-board-thanks-csv.ts +71 -0
- package/scripts/run/board/run-board-thanks-read.ts +112 -0
- package/scripts/run/board/run-board-thanks.ts +53 -0
- package/scripts/run/board/run-board-tick.ts +50 -21
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-cli.ts +2 -45
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry.ts +8 -1
- package/scripts/run/entry/run-entry-cli.ts +5 -0
- package/scripts/run/event/run-event-checks.ts +54 -0
- package/scripts/run/event/run-event-render.ts +2 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +55 -11
- package/scripts/run/event/run-event-stream.ts +27 -11
- package/scripts/run/merge/run-merge-cli.ts +7 -4
- package/scripts/run/merge/run-merge-ship.ts +61 -0
- package/scripts/run/merge/run-merge-steps.ts +7 -14
- package/scripts/run/merge/run-merge-token.ts +16 -2
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-step.ts +2 -0
- package/scripts/run/ship/run-ship-cli.ts +22 -2
- package/scripts/run/ship/run-ship-return.ts +31 -13
- package/scripts/run/ship/run-ship-review-steps.ts +17 -4
- package/scripts/run/ship/run-ship-stage.ts +1 -0
- package/scripts/run/wake/run-wake-await.ts +130 -0
- package/scripts/run/wake/run-wake-cli.ts +60 -71
- package/scripts/run/wake/run-wake-describe.ts +34 -2
- package/scripts/run/wake/run-wake-driver.ts +11 -4
- package/scripts/run/wake/run-wake-session.ts +20 -3
- package/scripts/run/wake/run-wake-stop.ts +37 -0
- package/scripts/run/wake/run-wake-supervise.ts +44 -0
- package/scripts/sync/claude-settings-merge.ts +144 -0
- package/scripts/sync/sync-ai-files.ts +28 -3
- package/scripts/sync/sync.ts +15 -0
- package/scripts/test/project-test-script.ts +73 -0
- package/scripts/test/skip-marker.ts +14 -1
- package/scripts/test/test-related.ts +3 -2
- package/scripts/test/test-unit-guard.ts +78 -14
- package/scripts/time-runtime/time-reported-failure.ts +11 -3
- package/scripts/uninstall/distributed-files.ts +92 -0
- package/scripts/uninstall/uninstall-plan.ts +119 -0
- package/scripts/uninstall/uninstall-report.ts +61 -0
- package/scripts/uninstall/uninstall.ts +206 -0
- package/templates/AGENTS.basic.md +4 -0
- package/templates/GEMINI.basic.md +6 -0
- package/dist/hooks/chunk-6V5OPIUU.js +0 -13
- package/scripts/lane/openai-review-broker.ts +0 -262
- package/scripts/run/board/run-board-track.ts +0 -22
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import {
|
|
3
|
+
constants,
|
|
4
|
+
cpSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
statSync,
|
|
11
|
+
} from 'node:fs'
|
|
12
|
+
import os from 'node:os'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
15
|
+
import { resolve_tsx_runner } from '#scripts/josh/josh-logic'
|
|
16
|
+
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
17
|
+
import { execaSync } from 'execa'
|
|
18
|
+
import { init_bootstrap } from './init-bootstrap'
|
|
19
|
+
import { init_dry_run_report, type FileChange } from './init-dry-run-report'
|
|
20
|
+
import { init_install } from './init-install'
|
|
21
|
+
import { PACKAGE_DIR, package_path } from './init-paths'
|
|
22
|
+
import { project_profile, type ProjectProfile } from './project-profile'
|
|
23
|
+
import { start_exec } from './start-exec'
|
|
24
|
+
|
|
25
|
+
// `josh init --dry-run` and `josh start --dry-run` answer "what would the setup change?" without
|
|
26
|
+
// a second copy of the setup's decisions: the real `josh init` runs on a scratch copy of the project,
|
|
27
|
+
// and the plan is the difference it left there. Every create, merge and skip is decided by the code
|
|
28
|
+
// that decides it for real, so the plan cannot drift from the run (#3830).
|
|
29
|
+
|
|
30
|
+
interface DryRunRequest {
|
|
31
|
+
root: string
|
|
32
|
+
profile: ProjectProfile | undefined
|
|
33
|
+
// Whether the setup meets a repository — `josh start` creates one before its setup runs.
|
|
34
|
+
has_git: boolean
|
|
35
|
+
is_install: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface DryRunSplit {
|
|
39
|
+
is_dry_run: boolean
|
|
40
|
+
rest: ReadonlyArray<string>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const DRY_RUN_FLAG = '--dry-run'
|
|
44
|
+
const INIT_SCRIPT = 'scripts/init/init.ts'
|
|
45
|
+
const SCRATCH_PREFIX = 'josh-init-dry-run-'
|
|
46
|
+
const DEFAULT_BRANCH = 'main'
|
|
47
|
+
const TITLE = 'josh init --dry-run — nothing was written. The setup would:'
|
|
48
|
+
|
|
49
|
+
function split_dry_run_flag(args: ReadonlyArray<string>): DryRunSplit {
|
|
50
|
+
return {
|
|
51
|
+
is_dry_run: args.includes(DRY_RUN_FLAG),
|
|
52
|
+
rest: args.filter((argument) => argument !== DRY_RUN_FLAG),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A directory is copied unless the project scan skips it: an ignored one, or a link — followed, a
|
|
57
|
+
// link to the project or an ancestor would copy it into itself until the path is too long.
|
|
58
|
+
function is_copied_directory(source: string): boolean {
|
|
59
|
+
return (
|
|
60
|
+
!lstatSync(source).isSymbolicLink() &&
|
|
61
|
+
!project_profile.is_ignored_directory(path.basename(source))
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A link to a file is copied as the file it points to, so no write in the scratch copy can follow a
|
|
66
|
+
// link back into the project; a dangling one is left out, as the project scan leaves out what it
|
|
67
|
+
// cannot read. The root itself is always copied: `cpSync` filters it too, so a project in a
|
|
68
|
+
// directory named `build` or `dist` would otherwise be planned as an empty one.
|
|
69
|
+
function is_copied(root: string, source: string): boolean {
|
|
70
|
+
if (source === root) return true
|
|
71
|
+
if (!existsSync(source)) return false
|
|
72
|
+
|
|
73
|
+
return !statSync(source).isDirectory() || is_copied_directory(source)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The scratch repository answers the setup's git questions as the project's does: one exists, and
|
|
77
|
+
// its origin is the same URL, so a GitHub project is planned as one.
|
|
78
|
+
function mirror_git(root: string, scratch: string): void {
|
|
79
|
+
start_exec.git_succeeds(['init', '--quiet', `--initial-branch=${DEFAULT_BRANCH}`], scratch)
|
|
80
|
+
const origin = git_spawn_sync.origin_url(root)
|
|
81
|
+
|
|
82
|
+
if (origin !== undefined) start_exec.git_succeeds(['remote', 'add', 'origin', origin], scratch)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function prepare_scratch(root: string, has_git: boolean): string {
|
|
86
|
+
const scratch = mkdtempSync(path.join(os.tmpdir(), SCRATCH_PREFIX))
|
|
87
|
+
|
|
88
|
+
// A copy-on-write clone where the file system supports one, so mirroring a large project costs
|
|
89
|
+
// next to nothing there; a plain copy elsewhere.
|
|
90
|
+
cpSync(root, scratch, {
|
|
91
|
+
recursive: true,
|
|
92
|
+
dereference: true,
|
|
93
|
+
mode: constants.COPYFILE_FICLONE,
|
|
94
|
+
filter: (source) => is_copied(root, source),
|
|
95
|
+
})
|
|
96
|
+
if (has_git) mirror_git(root, scratch)
|
|
97
|
+
|
|
98
|
+
return scratch
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function digest_of(file: string): string {
|
|
102
|
+
return createHash('sha256').update(readFileSync(file)).digest('hex')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A snapshot holds each file's digest, not its content, so a project with large files is compared
|
|
106
|
+
// without holding them in memory; only the files the setup changed are read in full.
|
|
107
|
+
function snapshot(root: string): Map<string, string> {
|
|
108
|
+
const files = project_profile.list_files(root)
|
|
109
|
+
|
|
110
|
+
return new Map(files.map((file) => [path.relative(root, file), digest_of(file)]))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function content_of(
|
|
114
|
+
root: string,
|
|
115
|
+
file: string,
|
|
116
|
+
digests: ReadonlyMap<string, string>,
|
|
117
|
+
): string | undefined {
|
|
118
|
+
return digests.has(file) ? readFileSync(path.join(root, file), 'utf8') : undefined
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The project itself still holds what the scratch copy held before the setup ran.
|
|
122
|
+
function changes_since(
|
|
123
|
+
request: DryRunRequest,
|
|
124
|
+
scratch: string,
|
|
125
|
+
before: ReadonlyMap<string, string>,
|
|
126
|
+
): Array<FileChange> {
|
|
127
|
+
const after = snapshot(scratch)
|
|
128
|
+
|
|
129
|
+
return init_dry_run_report.changed_files(before, after).map((change) => ({
|
|
130
|
+
path: change.path,
|
|
131
|
+
before: content_of(request.root, change.path, before),
|
|
132
|
+
after: content_of(scratch, change.path, after),
|
|
133
|
+
}))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// `--no-install` keeps the install out of the scratch copy, and the hand-off variable keeps this
|
|
137
|
+
// kit doing the setup itself — the scratch copy has no kit of its own to hand it to.
|
|
138
|
+
function run_init(scratch: string, profile: ProjectProfile | undefined): void {
|
|
139
|
+
const runner = resolve_tsx_runner()
|
|
140
|
+
const profile_args = profile === undefined ? [] : ['--profile', profile]
|
|
141
|
+
const args = [package_path(INIT_SCRIPT), init_install.NO_INSTALL_FLAG, ...profile_args]
|
|
142
|
+
const result = execaSync(runner.executable, [...runner.leading_arguments, ...args], {
|
|
143
|
+
cwd: scratch,
|
|
144
|
+
env: { [init_bootstrap.HANDOFF_ENV]: '1' },
|
|
145
|
+
all: true,
|
|
146
|
+
reject: false,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
if (result.exitCode !== 0) throw new Error(`the simulated josh init failed:\n${result.all}`)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function simulate(request: DryRunRequest): Array<FileChange> {
|
|
153
|
+
const scratch = prepare_scratch(request.root, request.has_git)
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const before = snapshot(scratch)
|
|
157
|
+
|
|
158
|
+
run_init(scratch, request.profile)
|
|
159
|
+
|
|
160
|
+
return changes_since(request, scratch, before)
|
|
161
|
+
} finally {
|
|
162
|
+
rmSync(scratch, { recursive: true, force: true })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// The same two questions the real run asks: whether to install at all, and whether this kit is the
|
|
167
|
+
// project's own or hands the setup to the one it installs.
|
|
168
|
+
function dependency_lines(root: string, is_install: boolean): Array<string> {
|
|
169
|
+
if (!is_install) return [`skip the install and the format (${init_install.NO_INSTALL_FLAG})`]
|
|
170
|
+
const steps = `run ${init_install.INSTALL_STEP.label}, then ${init_install.FORMAT_STEP.label}`
|
|
171
|
+
|
|
172
|
+
if (init_bootstrap.is_project_kit(PACKAGE_DIR, root)) return [steps]
|
|
173
|
+
|
|
174
|
+
return [`install ${KIT_PACKAGE_NAME} into the project and hand the setup to its josh init`, steps]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function plan_report(request: DryRunRequest, title: string = TITLE): string {
|
|
178
|
+
const changes = simulate(request)
|
|
179
|
+
|
|
180
|
+
return init_dry_run_report.format_report(
|
|
181
|
+
title,
|
|
182
|
+
changes,
|
|
183
|
+
dependency_lines(request.root, request.is_install),
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const init_dry_run = { DRY_RUN_FLAG, split_dry_run_flag, plan_report }
|
|
188
|
+
export { init_dry_run }
|
|
189
|
+
export type { DryRunRequest }
|
|
@@ -62,6 +62,13 @@ function run_post_init_steps(project_root: string): string | undefined {
|
|
|
62
62
|
return undefined
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const init_install = {
|
|
65
|
+
const init_install = {
|
|
66
|
+
NO_INSTALL_FLAG,
|
|
67
|
+
INSTALL_STEP,
|
|
68
|
+
FORMAT_STEP,
|
|
69
|
+
split_install_flag,
|
|
70
|
+
did_step_succeed,
|
|
71
|
+
run_post_init_steps,
|
|
72
|
+
}
|
|
66
73
|
export { init_install }
|
|
67
74
|
export type { InstallStep }
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { parse_jsonc } from '#scripts/config-merge/parse-jsonc'
|
|
2
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
3
|
+
import { init_logic_json_merge } from './init-logic-json-merge'
|
|
4
|
+
import { kit_development_versions } from './kit-development-versions'
|
|
5
|
+
|
|
6
|
+
// The tools a consumer has to resolve itself, as opposed to the ones only kit's own ESLint config
|
|
7
|
+
// imports. Those are optional kit peers, which pnpm does not install unlisted, so a consumer still
|
|
8
|
+
// needs them — `josh init` writes them, `josh sync` leaves them to it (#3711). A tool belongs here
|
|
9
|
+
// when the consumer runs its bin from the project root (eslint, prettier, tsc, playwright, cspell,
|
|
10
|
+
// lefthook), when a config the consumer owns imports it (`playwright.config.ts` → @playwright/test,
|
|
11
|
+
// @types/node), or when it is named by a string Prettier resolves from the consumer (the preset's
|
|
12
|
+
// plugins). `josh init` and `josh sync` both read this one list, so a tool added here reaches a fresh
|
|
13
|
+
// project and an already-initialized one alike.
|
|
14
|
+
const CONSUMER_RESOLVED_TOOLS = [
|
|
15
|
+
'eslint',
|
|
16
|
+
'prettier',
|
|
17
|
+
'typescript',
|
|
18
|
+
'@playwright/test',
|
|
19
|
+
'@ianvs/prettier-plugin-sort-imports',
|
|
20
|
+
'prettier-plugin-svelte',
|
|
21
|
+
'prettier-plugin-tailwindcss',
|
|
22
|
+
'cspell',
|
|
23
|
+
'@types/node',
|
|
24
|
+
]
|
|
25
|
+
// Only a project with Git gets `lefthook.yml`, so only that one needs the bin.
|
|
26
|
+
const LEFTHOOK_DEVELOPMENT_DEPENDENCY = 'lefthook'
|
|
27
|
+
const DECLARING_FIELDS = [
|
|
28
|
+
'dependencies',
|
|
29
|
+
'devDependencies',
|
|
30
|
+
'peerDependencies',
|
|
31
|
+
'optionalDependencies',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
function consumer_resolved_names(has_git: boolean): ReadonlyArray<string> {
|
|
35
|
+
return has_git
|
|
36
|
+
? [...CONSUMER_RESOLVED_TOOLS, LEFTHOOK_DEVELOPMENT_DEPENDENCY]
|
|
37
|
+
: CONSUMER_RESOLVED_TOOLS
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// What `josh init` adds: every kit peer — the ones only kit's own configs import included — and the
|
|
41
|
+
// tools above. Versions mirror kit's own devDependencies, so the consumer runs the toolchain kit is
|
|
42
|
+
// verified with.
|
|
43
|
+
function get_toolchain_development_dependencies(has_git: boolean): Record<string, string> {
|
|
44
|
+
return kit_development_versions.versions_of([
|
|
45
|
+
...kit_development_versions.peer_names(),
|
|
46
|
+
...consumer_resolved_names(has_git),
|
|
47
|
+
])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function is_declared(manifest: Record<string, unknown>, name: string): boolean {
|
|
51
|
+
return DECLARING_FIELDS.some((field) => {
|
|
52
|
+
const dependencies = manifest[field]
|
|
53
|
+
|
|
54
|
+
return json_value.is_record(dependencies) && Object.hasOwn(dependencies, name)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// What `josh sync` adds to an already-initialized consumer: only the tools above, and only the ones
|
|
59
|
+
// the manifest declares nowhere — a tool kept under `dependencies` is present, not missing.
|
|
60
|
+
function merge_consumer_resolved_development_deps(content: string, has_git: boolean): string {
|
|
61
|
+
const manifest = parse_jsonc(content)
|
|
62
|
+
const missing = consumer_resolved_names(has_git).filter((name) => !is_declared(manifest, name))
|
|
63
|
+
|
|
64
|
+
return init_logic_json_merge.merge_development_dependencies(
|
|
65
|
+
content,
|
|
66
|
+
kit_development_versions.versions_of(missing),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const init_logic_toolchain = {
|
|
71
|
+
consumer_resolved_names,
|
|
72
|
+
get_toolchain_development_dependencies,
|
|
73
|
+
merge_consumer_resolved_development_deps,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { init_logic_toolchain }
|
|
@@ -7,6 +7,7 @@ import { init_logic_json_merge } from './init-logic-json-merge'
|
|
|
7
7
|
import { init_logic_secretlint } from './init-logic-secretlint'
|
|
8
8
|
import { init_logic_sonar } from './init-logic-sonar'
|
|
9
9
|
import { init_logic_templates } from './init-logic-templates'
|
|
10
|
+
import { init_logic_toolchain } from './init-logic-toolchain'
|
|
10
11
|
import { init_logic_workspace } from './init-logic-workspace'
|
|
11
12
|
import { init_logic_yaml_merge } from './init-logic-yaml-merge'
|
|
12
13
|
|
|
@@ -417,6 +418,7 @@ const init_logic = {
|
|
|
417
418
|
...init_logic_sonar,
|
|
418
419
|
...init_logic_json_merge,
|
|
419
420
|
...init_logic_secretlint,
|
|
421
|
+
...init_logic_toolchain,
|
|
420
422
|
...init_logic_yaml_merge,
|
|
421
423
|
...init_logic_deploy_vps,
|
|
422
424
|
...distributed_paths,
|
package/scripts/init/init.ts
CHANGED
|
@@ -19,21 +19,27 @@ import { init_actions, PRETTIER_CONFIG_JS } from './init-actions'
|
|
|
19
19
|
import { init_ai_copy } from './init-ai-copy'
|
|
20
20
|
import { init_basic } from './init-basic'
|
|
21
21
|
import { init_bootstrap } from './init-bootstrap'
|
|
22
|
+
import { init_dry_run } from './init-dry-run'
|
|
22
23
|
import { init_file_action } from './init-file-action'
|
|
23
24
|
import { init_install } from './init-install'
|
|
24
25
|
import { init_logic } from './init-logic'
|
|
25
26
|
import { PACKAGE_DIR, PROJECT_ROOT } from './init-paths'
|
|
26
|
-
import { kit_development_versions } from './kit-development-versions'
|
|
27
|
-
import { kit_setup_state } from './kit-setup-state'
|
|
28
27
|
import { package_manager_pin } from './package-manager-pin'
|
|
29
28
|
import { project_profile, type ProjectProfile, type ProjectShape } from './project-profile'
|
|
29
|
+
import { working_tree } from './working-tree'
|
|
30
30
|
|
|
31
31
|
const PACKAGE_JSON = 'package.json'
|
|
32
32
|
const KIT_PACKAGE_NAME = '@joshuafolkken/kit'
|
|
33
33
|
const LEFTHOOK_BIN = 'lefthook'
|
|
34
34
|
const ARGUMENT_START_INDEX = 2
|
|
35
35
|
const INSTALL_HINT = '→ run `pnpm install` to install what package.json lists'
|
|
36
|
-
const INIT_USAGE = 'josh init [--profile basic|full] [--no-install]'
|
|
36
|
+
const INIT_USAGE = 'josh init [--profile basic|full] [--no-install] [--dry-run]'
|
|
37
|
+
|
|
38
|
+
interface InitRequest {
|
|
39
|
+
is_dry_run: boolean
|
|
40
|
+
is_install: boolean
|
|
41
|
+
requested: ProjectProfile | undefined
|
|
42
|
+
}
|
|
37
43
|
|
|
38
44
|
const engine_pin_schema = z.looseObject({ name: z.string(), version: z.string() })
|
|
39
45
|
|
|
@@ -71,27 +77,8 @@ function get_kit_self_dependency(): Record<string, string> {
|
|
|
71
77
|
return { [KIT_PACKAGE_NAME]: version }
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
// Each config `init` generates runs a CLI or imports types the consumer has to resolve itself
|
|
75
|
-
//
|
|
76
|
-
// Prettier, its preset plugins and @playwright/test come in as peers. Without them the first
|
|
77
|
-
// `josh gate` after `pnpm install` fails.
|
|
78
|
-
const TOOL_DEVELOPMENT_DEPENDENCIES = ['cspell', '@types/node']
|
|
79
|
-
const LEFTHOOK_DEVELOPMENT_DEPENDENCY = 'lefthook'
|
|
80
|
-
|
|
81
|
-
function tool_dependency_names(has_git: boolean): ReadonlyArray<string> {
|
|
82
|
-
return has_git
|
|
83
|
-
? [...TOOL_DEVELOPMENT_DEPENDENCIES, LEFTHOOK_DEVELOPMENT_DEPENDENCY]
|
|
84
|
-
: TOOL_DEVELOPMENT_DEPENDENCIES
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Versions mirror kit's own devDependencies, so the consumer runs the toolchain kit is verified with.
|
|
88
|
-
function get_toolchain_development_dependencies(has_git: boolean): Record<string, string> {
|
|
89
|
-
return kit_development_versions.versions_of([
|
|
90
|
-
...kit_development_versions.peer_names(),
|
|
91
|
-
...tool_dependency_names(has_git),
|
|
92
|
-
])
|
|
93
|
-
}
|
|
94
|
-
|
|
80
|
+
// Each config `init` generates runs a CLI or imports types the consumer has to resolve itself, so
|
|
81
|
+
// the toolchain is added here: without it the first `josh gate` after `pnpm install` fails.
|
|
95
82
|
function apply_dependency_merges(content: string, has_git = true): string {
|
|
96
83
|
const migrated = init_logic.strip_managed_postinstall(content)
|
|
97
84
|
const suggested = init_logic.get_suggested_scripts_for_content(migrated)
|
|
@@ -103,7 +90,7 @@ function apply_dependency_merges(content: string, has_git = true): string {
|
|
|
103
90
|
)
|
|
104
91
|
const with_toolchain = init_logic.merge_development_dependencies(
|
|
105
92
|
merged,
|
|
106
|
-
get_toolchain_development_dependencies(has_git),
|
|
93
|
+
init_logic.get_toolchain_development_dependencies(has_git),
|
|
107
94
|
)
|
|
108
95
|
const with_secretlint = init_logic.merge_secretlint_development_deps(with_toolchain)
|
|
109
96
|
|
|
@@ -147,6 +134,7 @@ function merged_manifest(existing: string, shape: ProjectShape): string {
|
|
|
147
134
|
return init_basic.with_recorded_profile(
|
|
148
135
|
apply_package_json_merges(existing, shape.has_git),
|
|
149
136
|
shape.profile,
|
|
137
|
+
shape.has_workflow,
|
|
150
138
|
)
|
|
151
139
|
}
|
|
152
140
|
|
|
@@ -284,14 +272,21 @@ function set_up_project(requested: ProjectProfile | undefined, is_install: boole
|
|
|
284
272
|
console.info('\n✅ Done.\n')
|
|
285
273
|
}
|
|
286
274
|
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
275
|
+
// A dry run sets nothing up, so it answers false and points nowhere onward.
|
|
276
|
+
function print_dry_run({ requested, is_install }: InitRequest): false {
|
|
277
|
+
const has_git = project_profile.has_git(PROJECT_ROOT)
|
|
278
|
+
|
|
279
|
+
console.info(
|
|
280
|
+
init_dry_run.plan_report({ root: PROJECT_ROOT, profile: requested, has_git, is_install }),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
294
285
|
|
|
286
|
+
function did_set_up_here(
|
|
287
|
+
{ requested, is_install }: InitRequest,
|
|
288
|
+
args: ReadonlyArray<string>,
|
|
289
|
+
): boolean {
|
|
295
290
|
const is_set_up_here = !is_install || !did_hand_off(args)
|
|
296
291
|
|
|
297
292
|
if (is_set_up_here) set_up_project(requested, is_install)
|
|
@@ -299,19 +294,43 @@ function main(args: ReadonlyArray<string> = []): boolean {
|
|
|
299
294
|
return is_set_up_here
|
|
300
295
|
}
|
|
301
296
|
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
297
|
+
// The flags are peeled off first, so what remains is exactly the `--profile` pair.
|
|
298
|
+
// Validated before the hand-off, so a malformed invocation is refused before kit is installed.
|
|
299
|
+
function parse_init_args(args: ReadonlyArray<string>): InitRequest {
|
|
300
|
+
const { is_dry_run, rest: run_args } = init_dry_run.split_dry_run_flag(args)
|
|
301
|
+
const { is_install, rest } = init_install.split_install_flag(run_args)
|
|
302
|
+
|
|
303
|
+
return { is_dry_run, is_install, requested: project_profile.requested_profile(rest, INIT_USAGE) }
|
|
304
|
+
}
|
|
308
305
|
|
|
309
|
-
|
|
306
|
+
// True when this process set the project up — false when it refused, or handed the run to the
|
|
307
|
+
// project's own kit, which then speaks for itself.
|
|
308
|
+
function main(args: ReadonlyArray<string> = []): boolean {
|
|
309
|
+
if (did_refuse_self_run(PACKAGE_DIR, PROJECT_ROOT)) return false
|
|
310
|
+
const request = parse_init_args(args)
|
|
311
|
+
|
|
312
|
+
return request.is_dry_run ? print_dry_run(request) : did_set_up_here(request, args)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Only the command a person typed is refused, before anything is written or installed. `josh start`
|
|
316
|
+
// calls `main` itself and decides about its own tree (#3835); a handed-off run follows the check
|
|
317
|
+
// its caller already passed, on a tree the kit install it just made has changed.
|
|
318
|
+
function did_refuse_dirty_tree(): boolean {
|
|
319
|
+
const refusal = init_bootstrap.is_handed_off()
|
|
320
|
+
? undefined
|
|
321
|
+
: working_tree.dirty_tree_refusal(PROJECT_ROOT, 'josh init')
|
|
322
|
+
|
|
323
|
+
if (refusal === undefined) return false
|
|
324
|
+
console.error(`\n${refusal}\n`)
|
|
325
|
+
process.exitCode = 1
|
|
326
|
+
|
|
327
|
+
return true
|
|
310
328
|
}
|
|
311
329
|
|
|
312
330
|
function run_cli(args: ReadonlyArray<string>): void {
|
|
313
331
|
try {
|
|
314
|
-
if (
|
|
332
|
+
if (did_refuse_dirty_tree()) return
|
|
333
|
+
main(args)
|
|
315
334
|
} catch (error) {
|
|
316
335
|
console.error(`\n✖ ${error_text.message_of(error)}\n`)
|
|
317
336
|
process.exitCode = 1
|
|
@@ -325,10 +344,11 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
|
325
344
|
const init = {
|
|
326
345
|
copy_ai_file: init_ai_copy.copy_ai_file,
|
|
327
346
|
apply_package_json_merges,
|
|
347
|
+
merged_manifest,
|
|
328
348
|
install_lefthook,
|
|
329
349
|
resolve_kit_package_manager,
|
|
330
350
|
}
|
|
331
351
|
|
|
332
352
|
// `init` is the entry point; `main` is exported for its own unit test, the way `sync.ts` exports the
|
|
333
|
-
// `main()` its self-run guard lives in.
|
|
334
|
-
export { init, main }
|
|
353
|
+
// `main()` its self-run guard lives in, and `run_cli` for the refusal only the typed command makes.
|
|
354
|
+
export { init, main, run_cli }
|
|
@@ -1,30 +1,24 @@
|
|
|
1
|
+
import { json_value } from '#scripts/lib/json-value'
|
|
1
2
|
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
2
3
|
import { start_exec } from './start-exec'
|
|
4
|
+
import { workflow_mark } from './workflow-mark'
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// Whether the commit checked out holds `josh start`'s setup — its manifest lists kit and records the
|
|
7
|
+
// GitHub workflow mark — the sign that the setup has already reached the branch, so `josh start` has
|
|
8
|
+
// no setup pull request to open. Kit alone is not enough: a committed `josh init` setup lacks the mark
|
|
9
|
+
// and, on `basic`, `.claude/settings.json`, which `josh start`'s setup then writes and only a setup
|
|
10
|
+
// pull request carries to `main` (#3835).
|
|
9
11
|
function is_kit_committed(root: string): boolean {
|
|
10
12
|
// `./` resolves the path from `root` rather than from the repository's top level.
|
|
11
13
|
const manifest = start_exec.git_read(['show', 'HEAD:./package.json'], root)
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function is_git_work_tree(root: string): boolean {
|
|
17
|
-
return start_exec.git_succeeds(['rev-parse', '--is-inside-work-tree'], root)
|
|
18
|
-
}
|
|
15
|
+
if (manifest?.includes(`"${KIT_PACKAGE_NAME}"`) !== true) return false
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
// still uncommitted, and `josh start` is what carries it to GitHub.
|
|
22
|
-
function start_hint(root: string): string | undefined {
|
|
23
|
-
if (!is_git_work_tree(root) || is_kit_committed(root)) return undefined
|
|
17
|
+
const parsed = json_value.parse_or_undefined(manifest)
|
|
24
18
|
|
|
25
|
-
return
|
|
19
|
+
return workflow_mark.is_recorded(json_value.is_record(parsed) ? parsed : undefined)
|
|
26
20
|
}
|
|
27
21
|
|
|
28
|
-
const kit_setup_state = { is_kit_committed
|
|
22
|
+
const kit_setup_state = { is_kit_committed }
|
|
29
23
|
|
|
30
24
|
export { kit_setup_state }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
// The configuration files of a formatter other than Prettier. A `basic` project holding one already
|
|
5
|
+
// formats its own files, so `josh init` gives it no Prettier and `josh format` / `josh gate` skip it
|
|
6
|
+
// (joshuafolkken/kit#3832).
|
|
7
|
+
const OWN_FORMATTER_CONFIGS = [
|
|
8
|
+
'biome.json',
|
|
9
|
+
'biome.jsonc',
|
|
10
|
+
'dprint.json',
|
|
11
|
+
'dprint.jsonc',
|
|
12
|
+
'.dprint.json',
|
|
13
|
+
'.dprint.jsonc',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
function find_config(root: string): string | undefined {
|
|
17
|
+
return OWN_FORMATTER_CONFIGS.find((name) => existsSync(path.join(root, name)))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const own_formatter = { find_config }
|
|
21
|
+
export { own_formatter }
|
|
@@ -4,6 +4,9 @@ import { repo_origin } from '#scripts/discovery/repo-origin'
|
|
|
4
4
|
import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
|
|
5
5
|
import { json_value } from '#scripts/lib/json-value'
|
|
6
6
|
import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
|
|
7
|
+
import { kit_written_paths } from './kit-written-paths'
|
|
8
|
+
import { own_formatter } from './own-formatter'
|
|
9
|
+
import { workflow_mark } from './workflow-mark'
|
|
7
10
|
|
|
8
11
|
type ProjectProfile = 'basic' | 'full'
|
|
9
12
|
|
|
@@ -13,10 +16,13 @@ interface ProfileResult {
|
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
interface ProjectShape extends ProfileResult {
|
|
16
|
-
|
|
19
|
+
// Web files, and no formatter of the project's own (`own-formatter.ts`).
|
|
20
|
+
should_use_prettier: boolean
|
|
17
21
|
has_typescript: boolean
|
|
18
22
|
has_git: boolean
|
|
19
23
|
has_github: boolean
|
|
24
|
+
// Set up by `josh start` for the GitHub Issue workflow (`workflow-mark.ts`).
|
|
25
|
+
has_workflow: boolean
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
const IGNORED_DIRECTORIES = new Set(['.git', 'node_modules', 'dist', 'build', '.svelte-kit'])
|
|
@@ -123,8 +129,12 @@ function resolve_profile(root: string, requested?: ProjectProfile): ProfileResul
|
|
|
123
129
|
return inferred_profile(manifest)
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
function is_ignored_directory(name: string): boolean {
|
|
133
|
+
return IGNORED_DIRECTORIES.has(name)
|
|
134
|
+
}
|
|
135
|
+
|
|
126
136
|
function should_visit(entry: Dirent): boolean {
|
|
127
|
-
return entry.isDirectory() && !
|
|
137
|
+
return entry.isDirectory() && !is_ignored_directory(entry.name)
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
function scan_directory(directory: string, pending: Array<string>): Array<string> {
|
|
@@ -136,26 +146,46 @@ function scan_directory(directory: string, pending: Array<string>): Array<string
|
|
|
136
146
|
.map((entry) => path.join(directory, entry.name)),
|
|
137
147
|
)
|
|
138
148
|
|
|
139
|
-
return entries.filter((entry) => entry.isFile()).map((entry) => path.
|
|
149
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => path.join(directory, entry.name))
|
|
140
150
|
}
|
|
141
151
|
|
|
142
|
-
|
|
152
|
+
// A file kit wrote says nothing about the project: counted, the `.pnpmfile.mjs` of the first
|
|
153
|
+
// `josh init` would read as the user's Web file on the second and bring Prettier in
|
|
154
|
+
// (joshuafolkken/kit#3713).
|
|
155
|
+
function is_project_file(root: string, file: string): boolean {
|
|
156
|
+
const relative_path = path.relative(root, file).split(path.sep).join(path.posix.sep)
|
|
157
|
+
|
|
158
|
+
return !kit_written_paths.is_kit_written(relative_path)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Every file under `root` outside the directories no setup reads — the one walk the project scan and
|
|
162
|
+
// the `josh init --dry-run` snapshot share.
|
|
163
|
+
function list_files(root: string): Array<string> {
|
|
143
164
|
const pending = [root]
|
|
144
|
-
const
|
|
165
|
+
const files: Array<string> = []
|
|
145
166
|
|
|
146
167
|
for (const directory of pending) {
|
|
147
|
-
|
|
168
|
+
files.push(...scan_directory(directory, pending))
|
|
148
169
|
}
|
|
149
170
|
|
|
150
|
-
return
|
|
171
|
+
return files
|
|
151
172
|
}
|
|
152
173
|
|
|
153
|
-
function
|
|
174
|
+
function file_extensions(root: string): Array<string> {
|
|
175
|
+
return list_files(root)
|
|
176
|
+
.filter((file) => is_project_file(root, file))
|
|
177
|
+
.map((file) => path.extname(file))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// A project that formats with its own tool gets no Prettier even with Web files: two formatters
|
|
181
|
+
// would rewrite each other's output (joshuafolkken/kit#3832).
|
|
182
|
+
function scan_files(root: string): { should_use_prettier: boolean; has_typescript: boolean } {
|
|
154
183
|
const extensions = file_extensions(root)
|
|
155
184
|
const has_web = extensions.some((extension) => WEB_EXTENSIONS.has(extension))
|
|
185
|
+
const should_use_prettier = has_web && own_formatter.find_config(root) === undefined
|
|
156
186
|
const has_typescript = extensions.some((extension) => TYPESCRIPT_EXTENSIONS.has(extension))
|
|
157
187
|
|
|
158
|
-
return {
|
|
188
|
+
return { should_use_prettier, has_typescript }
|
|
159
189
|
}
|
|
160
190
|
|
|
161
191
|
function has_github_remote(root: string): boolean {
|
|
@@ -164,18 +194,36 @@ function has_github_remote(root: string): boolean {
|
|
|
164
194
|
return url !== undefined && repo_origin.parse_origin_url(url) !== undefined
|
|
165
195
|
}
|
|
166
196
|
|
|
197
|
+
function has_git(root: string): boolean {
|
|
198
|
+
return existsSync(path.join(root, '.git'))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// The run is asked first, so a `josh start` whose setup has not written `package.json` yet reads it.
|
|
202
|
+
function has_workflow(root: string): boolean {
|
|
203
|
+
return workflow_mark.is_requested() || workflow_mark.is_recorded(read_manifest(root))
|
|
204
|
+
}
|
|
205
|
+
|
|
167
206
|
function inspect_project(root: string, requested?: ProjectProfile): ProjectShape {
|
|
168
207
|
const profile = resolve_profile(root, requested)
|
|
169
208
|
const files = scan_files(root)
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
return {
|
|
209
|
+
const is_git = has_git(root)
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
...profile,
|
|
213
|
+
...files,
|
|
214
|
+
has_git: is_git,
|
|
215
|
+
has_github: is_git && has_github_remote(root),
|
|
216
|
+
has_workflow: has_workflow(root),
|
|
217
|
+
}
|
|
173
218
|
}
|
|
174
219
|
|
|
175
220
|
const project_profile = {
|
|
176
221
|
parse_profile,
|
|
177
222
|
resolve_profile,
|
|
223
|
+
has_git,
|
|
178
224
|
inspect_project,
|
|
225
|
+
is_ignored_directory,
|
|
226
|
+
list_files,
|
|
179
227
|
requested_profile,
|
|
180
228
|
with_legacy_profile_names,
|
|
181
229
|
}
|