happyskills 0.23.0 → 0.24.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/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/src/api/push.js +10 -8
- package/src/commands/publish.js +11 -3
- package/src/commands/pull.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.24.0] - 2026-03-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Add merge commit support to `pull` and `publish` — when `pull` auto-merges local and remote changes without conflicts, the lock file stores `merge_parents` (the old base and remote head). On `publish`, these are sent as `parent_shas` to create a two-parent merge commit, preserving DAG history. Conflicts fall back to rebase semantics (single-parent commit). `merge_parents` is cleared after publish, on fast-forward, or when conflicts exist.
|
|
14
|
+
|
|
10
15
|
## [0.23.0] - 2026-03-30
|
|
11
16
|
|
|
12
17
|
### Added
|
package/package.json
CHANGED
package/src/api/push.js
CHANGED
|
@@ -12,13 +12,15 @@ const estimate_payload_size = (files) => {
|
|
|
12
12
|
return size
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const smart_push = (owner, repo, { version, message, files, visibility, base_commit, force }, on_progress) =>
|
|
15
|
+
const smart_push = (owner, repo, { version, message, files, visibility, base_commit, force, parent_shas }, on_progress) =>
|
|
16
16
|
catch_errors('Smart push failed', async () => {
|
|
17
17
|
const payload_size = estimate_payload_size(files)
|
|
18
18
|
|
|
19
19
|
if (payload_size < DIRECT_PUSH_THRESHOLD) {
|
|
20
20
|
// Small payload — use direct push
|
|
21
|
-
const
|
|
21
|
+
const body = { version, message, files, visibility, base_commit, force }
|
|
22
|
+
if (parent_shas) body.parent_shas = parent_shas
|
|
23
|
+
const [err, data] = await repos_api.push(owner, repo, body)
|
|
22
24
|
if (err) throw e('Direct push failed', err)
|
|
23
25
|
return data
|
|
24
26
|
}
|
|
@@ -27,9 +29,9 @@ const smart_push = (owner, repo, { version, message, files, visibility, base_com
|
|
|
27
29
|
const file_meta = files.map(f => ({ path: f.path, sha: f.sha, size: f.size }))
|
|
28
30
|
|
|
29
31
|
// Step 1: Initiate
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
const init_body = { version, message, files: file_meta, visibility, base_commit, force }
|
|
33
|
+
if (parent_shas) init_body.parent_shas = parent_shas
|
|
34
|
+
const [init_err, init_data] = await initiate_upload(owner, repo, init_body)
|
|
33
35
|
if (init_err) throw e('Upload initiation failed', init_err)
|
|
34
36
|
|
|
35
37
|
const { upload_id, presigned_urls } = init_data
|
|
@@ -52,9 +54,9 @@ const smart_push = (owner, repo, { version, message, files, visibility, base_com
|
|
|
52
54
|
if (upload_err) throw e('File uploads failed', upload_err)
|
|
53
55
|
|
|
54
56
|
// Step 3: Complete
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
const complete_body = { upload_id, version, message, files: file_meta, base_commit, force }
|
|
58
|
+
if (parent_shas) complete_body.parent_shas = parent_shas
|
|
59
|
+
const [complete_err, complete_data] = await complete_upload(owner, repo, complete_body)
|
|
58
60
|
if (complete_err) throw e('Upload completion failed', complete_err)
|
|
59
61
|
|
|
60
62
|
return complete_data
|
package/src/commands/publish.js
CHANGED
|
@@ -158,17 +158,19 @@ const run = (args) => catch_errors('Publish failed', async () => {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
// Read base_commit from lock file for divergence check
|
|
161
|
+
// Read base_commit and merge_parents from lock file for divergence check
|
|
162
162
|
const full_name_pre = `${workspace.slug}/${manifest.name}`
|
|
163
163
|
const project_root = find_project_root()
|
|
164
164
|
const [lock_err, lock_data] = await read_lock(project_root)
|
|
165
165
|
let base_commit = null
|
|
166
|
+
let merge_parents = null
|
|
166
167
|
if (!lock_err && lock_data) {
|
|
167
168
|
const all_skills = get_all_locked_skills(lock_data)
|
|
168
169
|
const suffix = `/${skill_name}`
|
|
169
170
|
const lock_key = Object.keys(all_skills).find(k => k.endsWith(suffix))
|
|
170
171
|
if (lock_key && all_skills[lock_key]) {
|
|
171
172
|
base_commit = all_skills[lock_key].base_commit || null
|
|
173
|
+
merge_parents = all_skills[lock_key].merge_parents || null
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
|
|
@@ -187,14 +189,18 @@ const run = (args) => catch_errors('Publish failed', async () => {
|
|
|
187
189
|
spinner.update(`Uploading files (${completed}/${total})...`)
|
|
188
190
|
}
|
|
189
191
|
const visibility = args.flags.public ? 'public' : 'private'
|
|
190
|
-
const
|
|
192
|
+
const push_options = {
|
|
191
193
|
version: manifest.version,
|
|
192
194
|
message: `Release ${manifest.version}`,
|
|
193
195
|
files: skill_files,
|
|
194
196
|
visibility,
|
|
195
197
|
base_commit: force ? null : base_commit,
|
|
196
198
|
force
|
|
197
|
-
}
|
|
199
|
+
}
|
|
200
|
+
if (merge_parents && !force) {
|
|
201
|
+
push_options.parent_shas = merge_parents
|
|
202
|
+
}
|
|
203
|
+
const [push_err, push_data] = await smart_push(workspace.slug, manifest.name, push_options, on_progress)
|
|
198
204
|
if (push_err) {
|
|
199
205
|
const last = push_err[push_err.length - 1]
|
|
200
206
|
if (last?.message?.includes('diverged') || last?.message?.includes('DIVERGED')) {
|
|
@@ -226,6 +232,8 @@ const run = (args) => catch_errors('Publish failed', async () => {
|
|
|
226
232
|
base_integrity: (!hash_err && integrity) ? integrity : null
|
|
227
233
|
}
|
|
228
234
|
if (!hash_err && integrity) updated_entry.integrity = integrity
|
|
235
|
+
delete updated_entry.merge_parents
|
|
236
|
+
delete updated_entry.conflict_files
|
|
229
237
|
const updated_skills = update_lock_skills(lock_data, { [lock_key]: updated_entry })
|
|
230
238
|
await write_lock(project_root, updated_skills)
|
|
231
239
|
} else {
|
package/src/commands/pull.js
CHANGED
|
@@ -205,6 +205,8 @@ const run = (args) => catch_errors('Pull failed', async () => {
|
|
|
205
205
|
version: cmp_data.head_version || lock_entry.version,
|
|
206
206
|
ref: clone_data.ref || lock_entry.ref
|
|
207
207
|
}
|
|
208
|
+
delete updated_entry.merge_parents
|
|
209
|
+
delete updated_entry.conflict_files
|
|
208
210
|
const merged_skills = update_lock_skills(lock_data, { [skill_name]: updated_entry })
|
|
209
211
|
const [wl_err] = await write_lock(lock_root(is_global, project_root), merged_skills)
|
|
210
212
|
if (wl_err) { spinner.fail('Failed to write lock file'); throw wl_err[0] }
|
|
@@ -440,8 +442,10 @@ const run = (args) => catch_errors('Pull failed', async () => {
|
|
|
440
442
|
}
|
|
441
443
|
if (conflict_files.length > 0) {
|
|
442
444
|
updated_entry.conflict_files = conflict_files
|
|
445
|
+
delete updated_entry.merge_parents // Conflicts → rebase fallback
|
|
443
446
|
} else {
|
|
444
447
|
delete updated_entry.conflict_files
|
|
448
|
+
updated_entry.merge_parents = [lock_entry.base_commit, cmp_data.head_commit]
|
|
445
449
|
}
|
|
446
450
|
const merged_skills = update_lock_skills(lock_data, { [skill_name]: updated_entry })
|
|
447
451
|
const [wl_err] = await write_lock(lock_root(is_global, project_root), merged_skills)
|