@fugood/bricks-project 2.25.0-beta.15 → 2.25.0-beta.16

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/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@fugood/bricks-project",
3
- "version": "2.25.0-beta.15",
3
+ "version": "2.25.0-beta.16",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "typecheck": "tsc --noEmit",
7
7
  "build": "bun scripts/build.js"
8
8
  },
9
9
  "dependencies": {
10
- "@fugood/bricks-cli": "^2.25.0-beta.15",
10
+ "@fugood/bricks-cli": "^2.25.0-beta.16",
11
11
  "@huggingface/gguf": "^0.3.2",
12
12
  "@iarna/toml": "^3.0.0",
13
13
  "@modelcontextprotocol/sdk": "^1.15.0",
package/package.json.bak CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@fugood/bricks-ctor",
3
- "version": "2.25.0-beta.15",
3
+ "version": "2.25.0-beta.16",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "typecheck": "tsc --noEmit",
7
7
  "build": "bun scripts/build.js"
8
8
  },
9
9
  "dependencies": {
10
- "@fugood/bricks-cli": "^2.25.0-beta.15",
10
+ "@fugood/bricks-cli": "^2.25.0-beta.16",
11
11
  "@huggingface/gguf": "^0.3.2",
12
12
  "@iarna/toml": "^3.0.0",
13
13
  "@modelcontextprotocol/sdk": "^1.15.0",
@@ -15,7 +15,15 @@ async function hasGitIdentity(cwd: string): Promise<boolean> {
15
15
  // If no identity is configured (typical for non-tech users), CTOR becomes the
16
16
  // main author via per-command `-c user.name/-c user.email` overrides so the
17
17
  // user's git config is left untouched.
18
- export async function buildCommitArgs(cwd: string, messages: string[]): Promise<string[]> {
18
+ //
19
+ // `extraFlags` are inserted between `commit` and the `-m` args (e.g.
20
+ // `['--no-verify']` to bypass pre-commit hooks for controlled cases like
21
+ // committing merge-conflict markers).
22
+ export async function buildCommitArgs(
23
+ cwd: string,
24
+ messages: string[],
25
+ extraFlags: string[] = [],
26
+ ): Promise<string[]> {
19
27
  const hasIdentity = await hasGitIdentity(cwd)
20
28
  const prefix = hasIdentity
21
29
  ? []
@@ -23,7 +31,7 @@ export async function buildCommitArgs(cwd: string, messages: string[]): Promise<
23
31
  const msgArgs: string[] = []
24
32
  for (const m of messages) msgArgs.push('-m', m)
25
33
  if (hasIdentity) msgArgs.push('-m', CTOR_COAUTHOR_TRAILER)
26
- return [...prefix, 'commit', ...msgArgs]
34
+ return [...prefix, 'commit', ...extraFlags, ...msgArgs]
27
35
  }
28
36
 
29
37
  export { CTOR_NAME, CTOR_EMAIL, CTOR_COAUTHOR_TRAILER }
@@ -0,0 +1,28 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
2
+
3
+ // Local sync marker: the git commit hash that was on HEAD when we last
4
+ // successfully pushed to the server (deploy or update-config) or pulled
5
+ // from it. Stored at the `.bricks/` top level (gitignored explicitly) — it's
6
+ // per-clone state, not a build artifact, so it doesn't belong under
7
+ // `.bricks/build/`.
8
+ //
9
+ // Why this exists: server's `bricks_project_last_commit_id` can be an opaque
10
+ // nanoid for content-only edits (config-editor saves), so it isn't a
11
+ // reliable git anchor for `git checkout` during pull. The local marker is
12
+ // always a real reachable commit hash from THIS clone.
13
+ const REL_PATH = '.bricks/.last-pushed-commit'
14
+
15
+ export async function readLastPushedCommit(cwd: string): Promise<string | null> {
16
+ try {
17
+ const content = await readFile(`${cwd}/${REL_PATH}`, 'utf8')
18
+ return content.trim() || null
19
+ } catch {
20
+ return null
21
+ }
22
+ }
23
+
24
+ export async function writeLastPushedCommit(cwd: string, commitId: string): Promise<void> {
25
+ if (!commitId) return
26
+ await mkdir(`${cwd}/.bricks`, { recursive: true })
27
+ await writeFile(`${cwd}/${REL_PATH}`, commitId)
28
+ }
package/tools/deploy.ts CHANGED
@@ -2,6 +2,7 @@ import { access, readFile, writeFile } from 'node:fs/promises'
2
2
  import { parseArgs } from 'util'
3
3
  import { sh } from './_shell'
4
4
  import { buildCommitArgs } from './_git-author'
5
+ import { writeLastPushedCommit } from './_last-pushed-commit'
5
6
 
6
7
  if (!process.env.BRICKS_RELEASE_SIGN) {
7
8
  process.env.BRICKS_RELEASE_SIGN = 'CTOR'
@@ -170,4 +171,10 @@ if (result.exitCode !== 0) {
170
171
  }
171
172
 
172
173
  const output = JSON.parse(result.stdout.toString())
174
+
175
+ // Record the commit we just pushed from so a later pull can use it as the
176
+ // merge base regardless of what the server stores in
177
+ // bricks_project_last_commit_id.
178
+ if (commitId) await writeLastPushedCommit(cwd, commitId)
179
+
173
180
  console.log(`${isModule ? 'Module' : 'App'} deployed: ${output.name}`)
package/tools/pull.ts CHANGED
@@ -4,6 +4,7 @@ import { join, relative } from 'node:path'
4
4
  import { format } from 'oxfmt'
5
5
  import { sh } from './_shell'
6
6
  import { buildCommitArgs } from './_git-author'
7
+ import { readLastPushedCommit, writeLastPushedCommit } from './_last-pushed-commit'
7
8
 
8
9
  // Directories whose .ts contents are entirely owned by the generator.
9
10
  // Anything under these dirs not present in the freshly pulled file list is an orphan.
@@ -75,30 +76,40 @@ if (result.exitCode !== 0) {
75
76
  }
76
77
  }
77
78
 
78
- const { files, lastCommitId } = JSON.parse(result.stdout.toString())
79
+ const { files, lastCommitId: serverLastCommitId } = JSON.parse(result.stdout.toString())
80
+
81
+ // The locally-saved commit (recorded by deploy/update-config) is the
82
+ // authoritative merge base for THIS clone — the server's value can be an
83
+ // opaque nanoid (config-only updates) or point to a commit other clients
84
+ // produced. Fall back to the server's value only if we have no local
85
+ // record yet.
86
+ const savedLocalCommitId = await readLastPushedCommit(cwd)
87
+ const baseCommitId = savedLocalCommitId || serverLastCommitId
88
+
89
+ const branchName = isModule
90
+ ? 'BRICKS_PROJECT_try-pull-module'
91
+ : 'BRICKS_PROJECT_try-pull-application'
79
92
 
80
- let useMain = false
81
93
  if (isGitRepo && !force) {
82
- console.log(`Checking commit ${lastCommitId}...`)
83
- const found = (await sh`cd ${cwd} && git rev-list -1 ${lastCommitId}`.nothrow().text())
94
+ console.log(`Checking commit ${baseCommitId}...`)
95
+ const found = (await sh`cd ${cwd} && git rev-list -1 ${baseCommitId}`.nothrow().text())
84
96
  .trim()
85
97
  .match(/^[\da-f]{40}$/)
86
98
 
87
- const commitId = (await sh`cd ${cwd} && git rev-parse HEAD`.text()).trim()
88
-
89
- if (commitId === lastCommitId) throw new Error('Commit not changed')
99
+ const headCommitId = (await sh`cd ${cwd} && git rev-parse HEAD`.text()).trim()
90
100
 
91
- const branchName = isModule
92
- ? 'BRICKS_PROJECT_try-pull-module'
93
- : 'BRICKS_PROJECT_try-pull-application'
101
+ if (headCommitId === serverLastCommitId) throw new Error('Commit not changed')
94
102
 
95
103
  await sh`cd ${cwd} && git branch -D ${branchName}`.nothrow()
96
104
 
105
+ // When the base commit isn't reachable in this clone (server stored a
106
+ // nanoid, or the commit was pruned), fall back to forking from current
107
+ // HEAD. The downstream merge into main collapses both paths into the
108
+ // same result, just with different merge bases.
97
109
  if (found) {
98
- await sh`cd ${cwd} && git checkout -b ${branchName} ${lastCommitId}`.nothrow()
110
+ await sh`cd ${cwd} && git checkout -b ${branchName} ${baseCommitId}`.nothrow()
99
111
  } else {
100
112
  await sh`cd ${cwd} && git checkout -b ${branchName}`
101
- useMain = true
102
113
  }
103
114
  }
104
115
 
@@ -152,9 +163,33 @@ if (isGitRepo) {
152
163
  const commitArgs = await buildCommitArgs(cwd, [commitMsg])
153
164
  await sh`cd ${cwd} && git ${commitArgs}`
154
165
  }
155
- if (!force && !useMain) {
156
- await sh`cd ${cwd} && git merge main`
166
+ if (!force) {
167
+ // Land the pulled commits on main with a single 3-way merge using
168
+ // baseCommit as the merge base. The user doesn't have to manage a side
169
+ // branch, and conflicts (if any) land in the working tree on main where
170
+ // auto-compile surfaces them as typecheck errors to resolve in-place.
171
+ await sh`cd ${cwd} && git checkout main`
172
+ const mergeResult = await sh`cd ${cwd} && git merge ${branchName} --no-edit`.nothrow()
173
+ if (mergeResult.exitCode !== 0) {
174
+ // Conflict markers are in the working tree — commit them so the tree
175
+ // is clean for auto-compile to detect, leaving the resolution to the
176
+ // user. Pre-commit hooks would reject markers (lint/format fail on
177
+ // invalid syntax), so bypass them for this controlled case.
178
+ await sh`cd ${cwd} && git add .`
179
+ const conflictArgs = await buildCommitArgs(
180
+ cwd,
181
+ ['chore(project): merge with conflicts (resolve in main)'],
182
+ ['--no-verify'],
183
+ )
184
+ await sh`cd ${cwd} && git ${conflictArgs}`
185
+ }
186
+ // The try-pull branch served its purpose; delete it so `git branch`
187
+ // stays tidy. The next pull recreates it anyway (line 103).
188
+ await sh`cd ${cwd} && git branch -D ${branchName}`.nothrow()
157
189
  }
190
+ // Record the new sync point so a follow-up pull starts from the right base.
191
+ const newHead = (await sh`cd ${cwd} && git rev-parse HEAD`.nothrow().text()).trim()
192
+ if (newHead) await writeLastPushedCommit(cwd, newHead)
158
193
  }
159
194
 
160
195
  console.log(
@@ -2,6 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises'
2
2
  import { parseArgs } from 'util'
3
3
  import { sh } from './_shell'
4
4
  import { buildCommitArgs } from './_git-author'
5
+ import { writeLastPushedCommit } from './_last-pushed-commit'
5
6
 
6
7
  const cwd = process.cwd()
7
8
 
@@ -107,4 +108,11 @@ if (result.exitCode !== 0) {
107
108
  }
108
109
 
109
110
  const output = JSON.parse(result.stdout.toString())
111
+
112
+ // Record the commit we just pushed from so a later pull can use it as the
113
+ // merge base regardless of what the server stores in
114
+ // bricks_project_last_commit_id (which may be an opaque nanoid for
115
+ // content-only edits).
116
+ if (commitId) await writeLastPushedCommit(cwd, commitId)
117
+
110
118
  console.log(`${isModule ? 'Module' : 'App'} config updated: ${output.target?.name || app.name}`)