@projitect/cli-internals 0.0.0-stage → 1.0.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.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/dist/applier.d.ts +12 -0
  3. package/dist/applier.d.ts.map +1 -0
  4. package/dist/applier.js +69 -0
  5. package/dist/applier.js.map +1 -0
  6. package/dist/commands/add.d.ts +34 -0
  7. package/dist/commands/add.d.ts.map +1 -0
  8. package/dist/commands/add.js +64 -0
  9. package/dist/commands/add.js.map +1 -0
  10. package/dist/commands/build.d.ts +39 -0
  11. package/dist/commands/build.d.ts.map +1 -0
  12. package/dist/commands/build.js +108 -0
  13. package/dist/commands/build.js.map +1 -0
  14. package/dist/commands/dispatch.d.ts +5 -0
  15. package/dist/commands/dispatch.d.ts.map +1 -0
  16. package/dist/commands/dispatch.js +211 -0
  17. package/dist/commands/dispatch.js.map +1 -0
  18. package/dist/commands/explain.d.ts +10 -0
  19. package/dist/commands/explain.d.ts.map +1 -0
  20. package/dist/commands/explain.js +46 -0
  21. package/dist/commands/explain.js.map +1 -0
  22. package/dist/commands/init.d.ts +33 -0
  23. package/dist/commands/init.d.ts.map +1 -0
  24. package/dist/commands/init.js +128 -0
  25. package/dist/commands/init.js.map +1 -0
  26. package/dist/commands/inspect-json.d.ts +51 -0
  27. package/dist/commands/inspect-json.d.ts.map +1 -0
  28. package/dist/commands/inspect-json.js +58 -0
  29. package/dist/commands/inspect-json.js.map +1 -0
  30. package/dist/commands/inspect.d.ts +41 -0
  31. package/dist/commands/inspect.d.ts.map +1 -0
  32. package/dist/commands/inspect.js +74 -0
  33. package/dist/commands/inspect.js.map +1 -0
  34. package/dist/commands/remodel.d.ts +22 -0
  35. package/dist/commands/remodel.d.ts.map +1 -0
  36. package/dist/commands/remodel.js +49 -0
  37. package/dist/commands/remodel.js.map +1 -0
  38. package/dist/config-cascade.d.ts +25 -0
  39. package/dist/config-cascade.d.ts.map +1 -0
  40. package/dist/config-cascade.js +45 -0
  41. package/dist/config-cascade.js.map +1 -0
  42. package/dist/differ.d.ts +31 -0
  43. package/dist/differ.d.ts.map +1 -0
  44. package/dist/differ.js +134 -0
  45. package/dist/differ.js.map +1 -0
  46. package/dist/edit-pjt.d.ts +19 -0
  47. package/dist/edit-pjt.d.ts.map +1 -0
  48. package/dist/edit-pjt.js +40 -0
  49. package/dist/edit-pjt.js.map +1 -0
  50. package/dist/filesystem-impl.d.ts +25 -0
  51. package/dist/filesystem-impl.d.ts.map +1 -0
  52. package/dist/filesystem-impl.js +153 -0
  53. package/dist/filesystem-impl.js.map +1 -0
  54. package/dist/git.d.ts +29 -0
  55. package/dist/git.d.ts.map +1 -0
  56. package/dist/git.js +63 -0
  57. package/dist/git.js.map +1 -0
  58. package/dist/index.d.ts +22 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +21 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/ipc/client.d.ts +11 -0
  63. package/dist/ipc/client.d.ts.map +1 -0
  64. package/dist/ipc/client.js +45 -0
  65. package/dist/ipc/client.js.map +1 -0
  66. package/dist/ipc/index.d.ts +5 -0
  67. package/dist/ipc/index.d.ts.map +1 -0
  68. package/dist/ipc/index.js +5 -0
  69. package/dist/ipc/index.js.map +1 -0
  70. package/dist/ipc/protocol.d.ts +251 -0
  71. package/dist/ipc/protocol.d.ts.map +1 -0
  72. package/dist/ipc/protocol.js +41 -0
  73. package/dist/ipc/protocol.js.map +1 -0
  74. package/dist/ipc/server.d.ts +9 -0
  75. package/dist/ipc/server.d.ts.map +1 -0
  76. package/dist/ipc/server.js +23 -0
  77. package/dist/ipc/server.js.map +1 -0
  78. package/dist/ipc/transport.d.ts +12 -0
  79. package/dist/ipc/transport.d.ts.map +1 -0
  80. package/dist/ipc/transport.js +19 -0
  81. package/dist/ipc/transport.js.map +1 -0
  82. package/dist/loader.d.ts +45 -0
  83. package/dist/loader.d.ts.map +1 -0
  84. package/dist/loader.js +65 -0
  85. package/dist/loader.js.map +1 -0
  86. package/dist/lockfile.d.ts +22 -0
  87. package/dist/lockfile.d.ts.map +1 -0
  88. package/dist/lockfile.js +59 -0
  89. package/dist/lockfile.js.map +1 -0
  90. package/dist/permissions.d.ts +20 -0
  91. package/dist/permissions.d.ts.map +1 -0
  92. package/dist/permissions.js +86 -0
  93. package/dist/permissions.js.map +1 -0
  94. package/dist/plan.d.ts +278 -0
  95. package/dist/plan.d.ts.map +1 -0
  96. package/dist/plan.js +212 -0
  97. package/dist/plan.js.map +1 -0
  98. package/dist/platform/file-system.d.ts +3 -0
  99. package/dist/platform/file-system.d.ts.map +1 -0
  100. package/dist/platform/file-system.js +109 -0
  101. package/dist/platform/file-system.js.map +1 -0
  102. package/dist/platform/index.d.ts +17 -0
  103. package/dist/platform/index.d.ts.map +1 -0
  104. package/dist/platform/index.js +20 -0
  105. package/dist/platform/index.js.map +1 -0
  106. package/dist/platform/process-runner.d.ts +41 -0
  107. package/dist/platform/process-runner.d.ts.map +1 -0
  108. package/dist/platform/process-runner.js +120 -0
  109. package/dist/platform/process-runner.js.map +1 -0
  110. package/dist/platform/stdio.d.ts +9 -0
  111. package/dist/platform/stdio.d.ts.map +1 -0
  112. package/dist/platform/stdio.js +11 -0
  113. package/dist/platform/stdio.js.map +1 -0
  114. package/dist/platform/terminal.d.ts +3 -0
  115. package/dist/platform/terminal.d.ts.map +1 -0
  116. package/dist/platform/terminal.js +81 -0
  117. package/dist/platform/terminal.js.map +1 -0
  118. package/dist/pm.d.ts +31 -0
  119. package/dist/pm.d.ts.map +1 -0
  120. package/dist/pm.js +91 -0
  121. package/dist/pm.js.map +1 -0
  122. package/dist/post-apply.d.ts +20 -0
  123. package/dist/post-apply.d.ts.map +1 -0
  124. package/dist/post-apply.js +29 -0
  125. package/dist/post-apply.js.map +1 -0
  126. package/dist/project-file.d.ts +8 -0
  127. package/dist/project-file.d.ts.map +1 -0
  128. package/dist/project-file.js +12 -0
  129. package/dist/project-file.js.map +1 -0
  130. package/dist/region.d.ts +105 -0
  131. package/dist/region.d.ts.map +1 -0
  132. package/dist/region.js +94 -0
  133. package/dist/region.js.map +1 -0
  134. package/dist/remover.d.ts +16 -0
  135. package/dist/remover.d.ts.map +1 -0
  136. package/dist/remover.js +88 -0
  137. package/dist/remover.js.map +1 -0
  138. package/package.json +52 -3
  139. package/src/applier.ts +91 -0
  140. package/src/commands/add.ts +88 -0
  141. package/src/commands/build.ts +156 -0
  142. package/src/commands/dispatch.ts +345 -0
  143. package/src/commands/explain.ts +81 -0
  144. package/src/commands/init.ts +193 -0
  145. package/src/commands/inspect-json.ts +68 -0
  146. package/src/commands/inspect.ts +106 -0
  147. package/src/commands/remodel.ts +72 -0
  148. package/src/config-cascade.ts +60 -0
  149. package/src/differ.ts +188 -0
  150. package/src/edit-pjt.ts +56 -0
  151. package/src/filesystem-impl.ts +230 -0
  152. package/src/git.ts +87 -0
  153. package/src/index.ts +54 -0
  154. package/src/ipc/client.ts +108 -0
  155. package/src/ipc/index.ts +12 -0
  156. package/src/ipc/protocol.ts +48 -0
  157. package/src/ipc/server.ts +62 -0
  158. package/src/ipc/transport.ts +28 -0
  159. package/src/loader.ts +115 -0
  160. package/src/lockfile.ts +81 -0
  161. package/src/permissions.ts +106 -0
  162. package/src/plan.ts +459 -0
  163. package/src/platform/file-system.ts +155 -0
  164. package/src/platform/index.ts +26 -0
  165. package/src/platform/process-runner.ts +150 -0
  166. package/src/platform/stdio.ts +11 -0
  167. package/src/platform/terminal.ts +89 -0
  168. package/src/pm.ts +131 -0
  169. package/src/post-apply.ts +45 -0
  170. package/src/project-file.ts +15 -0
  171. package/src/region.ts +139 -0
  172. package/src/remover.ts +127 -0
  173. package/README.md +0 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remover.js","sourceRoot":"","sources":["../src/remover.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@projitect/cli-internals",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0",
4
+ "description": "Engine for the projitect CLI — loader, planner, differ, applier, and command dispatchers.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/projitect/projitect.git",
8
+ "directory": "packages/cli-internals"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./bin": {
18
+ "types": "./dist/bin.d.ts",
19
+ "import": "./dist/bin.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src",
25
+ "!**/*.tsbuildinfo"
26
+ ],
27
+ "dependencies": {
28
+ "@nunofyobiz/effect-extras": "3.2.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "22.19.19",
32
+ "effect": "4.0.0-rc.111",
33
+ "typescript": "5.9.3",
34
+ "@projitect/blueprint": "1.0.0",
35
+ "@projitect/blueprint-gitignore": "1.0.0",
36
+ "@projitect/blueprint-markdown": "1.0.0",
37
+ "@projitect/blueprint-next": "1.0.0",
38
+ "@projitect/blueprint-react": "1.0.0",
39
+ "@projitect/core": "1.0.0",
40
+ "@projitect/test-kit": "1.0.0"
41
+ },
42
+ "peerDependencies": {
43
+ "effect": "4.0.0-rc.111",
44
+ "@projitect/blueprint": "1.0.0",
45
+ "@projitect/core": "1.0.0"
46
+ },
47
+ "engines": {
48
+ "node": ">=22.12.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsc --build",
52
+ "build:clean": "tsc --build --clean",
53
+ "tc": "tsc --build"
54
+ }
6
55
  }
package/src/applier.ts ADDED
@@ -0,0 +1,91 @@
1
+ import { Effect, Match } from "effect"
2
+ import { RecordX } from "@nunofyobiz/effect-extras"
3
+ import { BlueprintFileSystem } from "@projitect/core"
4
+ import type { Errors } from "@projitect/core"
5
+ import type { FilePlan, ProjectPlan, RegionPlanFile, MergePlanFile } from "./plan.js"
6
+ import { readIfExists } from "./project-file.js"
7
+ import { findRegion, renderRegion, upsertRegion } from "./region.js"
8
+
9
+ /**
10
+ * Apply a plan through the project `BlueprintFileSystem` (which creates parent directories). Used by `pjt remodel` (non-destructive) and `pjt build` (after wiping
11
+ * the project tree). Returns the list of paths actually written.
12
+ */
13
+ export const applyPlan = (params: {
14
+ readonly plan: ProjectPlan
15
+ }): Effect.Effect<
16
+ readonly string[],
17
+ | Errors.RegionMissingEnd
18
+ | Errors.RegionDuplicate
19
+ | Errors.FsWriteFailed
20
+ | Errors.FsPermissionDenied,
21
+ BlueprintFileSystem
22
+ > => {
23
+ const { plan } = params
24
+ return Effect.gen(function* () {
25
+ const fs = yield* BlueprintFileSystem
26
+ const written: string[] = []
27
+ for (const file of plan.files) {
28
+ const current = yield* readIfExists(file.path)
29
+ const next = yield* nextContent({ file, current })
30
+ if (next === null) {
31
+ continue
32
+ } // Seed-mode no-op
33
+ if (next === current) {
34
+ continue
35
+ } // Already in sync
36
+ yield* fs.writeFile(file.path, next)
37
+ written.push(file.path)
38
+ }
39
+ return written
40
+ })
41
+ }
42
+
43
+ const nextContent = (params: {
44
+ readonly file: FilePlan
45
+ readonly current: string | null
46
+ }): Effect.Effect<string | null, Errors.RegionMissingEnd | Errors.RegionDuplicate> =>
47
+ Match.valueTags(params.file, {
48
+ Region: (file) => nextRegion(file, params.current),
49
+ Merge: (file) => Effect.succeed(nextMerge(file, params.current)),
50
+ Owned: (file) => Effect.succeed(file.content),
51
+ Seed: (file) => Effect.succeed(params.current === null ? file.content : null),
52
+ })
53
+
54
+ const nextRegion = (
55
+ file: RegionPlanFile,
56
+ current: string | null,
57
+ ): Effect.Effect<string, Errors.RegionMissingEnd | Errors.RegionDuplicate> =>
58
+ Effect.gen(function* () {
59
+ let content = current ?? ""
60
+ for (const region of file.regions) {
61
+ const found = yield* findRegion({
62
+ fileContent: content,
63
+ ownerId: region.ownerId,
64
+ commentPrefix: file.commentPrefix,
65
+ commentSuffix: file.commentSuffix,
66
+ path: file.path,
67
+ })
68
+ const rendered = renderRegion({
69
+ ownerId: region.ownerId,
70
+ commentPrefix: file.commentPrefix,
71
+ commentSuffix: file.commentSuffix,
72
+ content: region.content,
73
+ })
74
+ content = upsertRegion({ fileContent: content, existing: found, rendered })
75
+ }
76
+ return content
77
+ })
78
+
79
+ const nextMerge = (file: MergePlanFile, current: string | null): string => {
80
+ const existing = current === null ? {} : (safeParseJson(current) ?? {})
81
+ const merged = RecordX.deepMerge(existing, file.value)
82
+ return `${JSON.stringify(merged, null, 2)}\n`
83
+ }
84
+
85
+ const safeParseJson = (text: string): unknown => {
86
+ try {
87
+ return JSON.parse(text) as unknown
88
+ } catch {
89
+ return null
90
+ }
91
+ }
@@ -0,0 +1,88 @@
1
+ import { Effect } from "effect"
2
+ import { Errors } from "@projitect/core"
3
+ import type { ProjitectConfig } from "@projitect/core"
4
+ import { computeSplice, resolveSections } from "@projitect/blueprint"
5
+ import type { ProjitectPackageMetadata, SectionStrategy } from "@projitect/blueprint"
6
+ import { detect, installDev as installDevelopment, readProjitectMetadata } from "../pm.js"
7
+ import { splice } from "../edit-pjt.js"
8
+
9
+ export interface AddResult {
10
+ readonly pm: string
11
+ readonly pkg: string
12
+ readonly metadata: ProjitectPackageMetadata
13
+ readonly sectionsAdded: readonly string[]
14
+ readonly splicedIntoBlueprintFile: boolean
15
+ /** The user picked no sections (or quit the prompt); `.pjt.ts` was left untouched. */
16
+ readonly selectionCancelled: boolean
17
+ }
18
+
19
+ /**
20
+ * `pjt add <package>` — install a blueprint package and splice it into `.pjt.ts`.
21
+ *
22
+ * Flow:
23
+ * 1. Detect package manager (pnpm / yarn / bun / npm).
24
+ * 2. Shell out `<pm> add -D <package>`.
25
+ * 3. Read the installed package's `package.json` for the `"projitect"` metadata field.
26
+ * - If present and `type: "blueprint-set"`: section selection resolves per `strategy`
27
+ * (all / explicit / ask).
28
+ * - If present and `type: "blueprint"`: a single call line, strategy ignored.
29
+ * - If absent: fails with `pjt.add.metadata-missing`. The package stays installed; the
30
+ * message tells the user to import it in `.pjt.ts` by hand and run `pjt remodel`.
31
+ * 4. Splice the import + call lines into `.pjt.ts` between the convention markers
32
+ * (`pjt:imports` and `pjt:blueprints`).
33
+ */
34
+ export const add = <R = never>(params: {
35
+ readonly config: ProjitectConfig.ProjitectConfig
36
+ readonly pkg: string
37
+ readonly strategy: SectionStrategy<R>
38
+ }): Effect.Effect<AddResult, Errors.ProjitectError, R> =>
39
+ Effect.gen(function* () {
40
+ const pm = yield* detect({ projectRoot: params.config.projectRoot })
41
+ yield* installDevelopment({ projectRoot: params.config.projectRoot, pm, pkg: params.pkg })
42
+ const metadata = yield* readProjitectMetadata({
43
+ projectRoot: params.config.projectRoot,
44
+ pkg: params.pkg,
45
+ })
46
+
47
+ if (metadata === null) {
48
+ return yield* new Errors.AddMetadataMissing({
49
+ id: "pjt.add.metadata-missing",
50
+ pkg: params.pkg,
51
+ blueprintFile: params.config.blueprintFile,
52
+ message:
53
+ `${params.pkg} was installed but has no \`"projitect"\` metadata in its package.json, so \`pjt add\` can't splice it into ${params.config.blueprintFile}. ` +
54
+ `Import it in ${params.config.blueprintFile} by hand, then run \`pjt remodel\`.`,
55
+ })
56
+ }
57
+
58
+ const selection = yield* resolveSections({ metadata, strategy: params.strategy })
59
+
60
+ if (selection._tag === "Nothing") {
61
+ return {
62
+ pm,
63
+ pkg: params.pkg,
64
+ metadata,
65
+ sectionsAdded: [],
66
+ splicedIntoBlueprintFile: false,
67
+ selectionCancelled: true,
68
+ }
69
+ }
70
+
71
+ const { importLine, callLines, sectionsAdded } = computeSplice({ metadata, selection })
72
+
73
+ yield* splice({
74
+ projectRoot: params.config.projectRoot,
75
+ blueprintFile: params.config.blueprintFile,
76
+ importLine,
77
+ callLines,
78
+ })
79
+
80
+ return {
81
+ pm,
82
+ pkg: params.pkg,
83
+ metadata,
84
+ sectionsAdded,
85
+ splicedIntoBlueprintFile: true,
86
+ selectionCancelled: false,
87
+ }
88
+ })
@@ -0,0 +1,156 @@
1
+ import { promises as fs } from "node:fs"
2
+ import path from "node:path"
3
+ import { Effect } from "effect"
4
+ import type { Terminal } from "effect"
5
+ import { Prompt } from "effect/unstable/cli"
6
+ import { StructX } from "@nunofyobiz/effect-extras"
7
+ import { Errors } from "@projitect/core"
8
+ import type { ProjitectConfig } from "@projitect/core"
9
+ import { ensureGitRepo, gitStatus } from "../git.js"
10
+ import type { ProcessSpawner } from "../platform/process-runner.js"
11
+ import { remodel } from "./remodel.js"
12
+ import type { RemodelResult } from "./remodel.js"
13
+
14
+ export interface BuildResult {
15
+ /** True when the confirmation prompt was declined, so nothing was wiped, written or run. */
16
+ readonly cancelled: boolean
17
+ readonly wiped: readonly string[]
18
+ readonly remodel: RemodelResult
19
+ }
20
+
21
+ /**
22
+ * Paths that survive `pjt build --force`. Everything else under projectRoot is deleted before
23
+ * the plan re-applies. This intentionally preserves the manifest (`package.json`), the blueprint
24
+ * file, the lockfile, the dependency store, the git history, and PM lockfiles so the project is
25
+ * recoverable.
26
+ */
27
+ const PRESERVED = new Set<string>([
28
+ ".git",
29
+ "node_modules",
30
+ ".pjt.ts",
31
+ ".pjt.lock",
32
+ "package.json",
33
+ "pnpm-lock.yaml",
34
+ "package-lock.json",
35
+ "yarn.lock",
36
+ "bun.lockb",
37
+ ])
38
+
39
+ /**
40
+ * `pjt build --force` — wipe the project tree (modulo preserved entries) and re-apply the plan
41
+ * from scratch.
42
+ *
43
+ * Safety net:
44
+ * 1. `--force` is required (the dispatcher's flag config enforces this at the CLI layer).
45
+ * 2. The project must be a git repository (`pjt.git.not-a-repo` otherwise).
46
+ * 3. The working tree must be clean (`pjt.apply.dirty-git`), unless `--force-dirty` is set.
47
+ * 4. An interactive confirmation prompt fires unless `--yes` is passed.
48
+ */
49
+ type BuildError = Errors.ProjitectError | Terminal.QuitError
50
+ type BuildRequirements = Prompt.Environment
51
+
52
+ export const build = (params: {
53
+ readonly config: ProjitectConfig.ProjitectConfig
54
+ readonly force: boolean
55
+ readonly forceDirty: boolean
56
+ readonly yes: boolean
57
+ readonly spawner?: ProcessSpawner
58
+ }): Effect.Effect<BuildResult, BuildError, BuildRequirements> =>
59
+ Effect.gen(function* () {
60
+ if (!params.force) {
61
+ return yield* new Errors.ApplyDirtyGit({
62
+ id: "pjt.apply.dirty-git",
63
+ command: "build",
64
+ message:
65
+ "`pjt build` requires the `--force` flag. This command is destructive — it wipes the project tree before re-applying.",
66
+ })
67
+ }
68
+
69
+ yield* ensureGitRepo({ projectRoot: params.config.projectRoot })
70
+
71
+ if (!params.forceDirty) {
72
+ const status = yield* gitStatus({ projectRoot: params.config.projectRoot })
73
+ if (!status.clean) {
74
+ return yield* new Errors.ApplyDirtyGit({
75
+ id: "pjt.apply.dirty-git",
76
+ command: "build",
77
+ message:
78
+ `Git working tree is dirty (${status.lines.length} uncommitted change${status.lines.length === 1 ? "" : "s"}). ` +
79
+ "Commit your changes (`git add -A && git commit -m wip`) and re-run `pjt build --force`, or pass `--force-dirty` to wipe anyway.",
80
+ })
81
+ }
82
+ }
83
+
84
+ if (!params.yes) {
85
+ const confirmed = yield* Prompt.confirm({
86
+ message: `This will delete everything under ${params.config.projectRoot} except .git, node_modules, .pjt.ts, .pjt.lock, package.json, and the PM lockfile. Continue?`,
87
+ initial: false,
88
+ })
89
+ if (!confirmed) {
90
+ return {
91
+ cancelled: true,
92
+ wiped: [],
93
+ remodel: { written: [], removed: [], ran: [] },
94
+ }
95
+ }
96
+ }
97
+
98
+ const wiped = yield* wipeTree(params.config.projectRoot)
99
+ const remodelResult = yield* remodel({
100
+ config: params.config,
101
+ ...StructX.defined("spawner", params.spawner),
102
+ })
103
+ return { cancelled: false, wiped, remodel: remodelResult }
104
+ })
105
+
106
+ const count = (n: number, singular: string, plural: string): string =>
107
+ `${n} ${n === 1 ? singular : plural}`
108
+
109
+ /**
110
+ * The human summary `pjt build` prints. Cancellation is the declined prompt, not "nothing
111
+ * happened": a build that only ran post-apply commands is a success and lists them.
112
+ */
113
+ export const renderBuildResult = (result: BuildResult): string => {
114
+ const { cancelled, wiped, remodel: applied } = result
115
+ const lines = cancelled
116
+ ? ["Cancelled. No changes made."]
117
+ : [
118
+ ...(wiped.length > 0
119
+ ? [`Wiped ${count(wiped.length, "entry", "entries")}:`, ...wiped.map((p) => ` ${p}`)]
120
+ : []),
121
+ ...(applied.written.length > 0
122
+ ? [
123
+ `Rebuilt ${count(applied.written.length, "file", "files")} from the plan:`,
124
+ ...applied.written.map((p) => ` ${p}`),
125
+ ]
126
+ : []),
127
+ ...(applied.ran.length > 0
128
+ ? [
129
+ `Ran ${count(applied.ran.length, "command", "commands")}:`,
130
+ ...applied.ran.map((c) => ` ${c}`),
131
+ ]
132
+ : []),
133
+ ]
134
+ return `${lines.join("\n")}\n`
135
+ }
136
+
137
+ const wipeTree = (projectRoot: string): Effect.Effect<readonly string[], Errors.FsWriteFailed> =>
138
+ Effect.tryPromise({
139
+ try: async () => {
140
+ const entries = await fs.readdir(projectRoot)
141
+ const toRemove = entries.filter((name) => !PRESERVED.has(name))
142
+ await Promise.all(
143
+ toRemove.map((name) =>
144
+ fs.rm(path.join(projectRoot, name), { recursive: true, force: true }),
145
+ ),
146
+ )
147
+ return toRemove
148
+ },
149
+ catch: (e) =>
150
+ new Errors.FsWriteFailed({
151
+ id: "pjt.fs.write-failed",
152
+ path: projectRoot,
153
+ cause: e instanceof Error ? e.message : String(e),
154
+ message: `Failed to wipe project tree at ${projectRoot}`,
155
+ }),
156
+ })