@playcraft/cli 0.1.7 → 0.1.8
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.
|
@@ -596,7 +596,7 @@ async function executeGitCommit(state, scopeKey, operation, payload) {
|
|
|
596
596
|
return persist(state, scopeKey, transitionOperation(operation, 'failed', { error: 'invalid_git_commit' }));
|
|
597
597
|
}
|
|
598
598
|
try {
|
|
599
|
-
const sha = await commitWorkspaceGit(await workspaceRoot(state, operation.workspaceId), parsed.data);
|
|
599
|
+
const sha = await commitWorkspaceGit(await workspaceRoot(state, operation.workspaceId), parsed.data, boundBranch(state, operation));
|
|
600
600
|
const accepted = await persist(state, scopeKey, transitionOperation(operation, 'accepted'));
|
|
601
601
|
await recordNativeEvents(state, accepted, [
|
|
602
602
|
{
|
|
@@ -617,7 +617,7 @@ async function executeGitPush(state, scopeKey, operation, payload) {
|
|
|
617
617
|
return persist(state, scopeKey, transitionOperation(operation, 'failed', { error: 'remote_not_configurable' }));
|
|
618
618
|
}
|
|
619
619
|
try {
|
|
620
|
-
await pushWorkspaceGit(await workspaceRoot(state, operation.workspaceId));
|
|
620
|
+
await pushWorkspaceGit(await workspaceRoot(state, operation.workspaceId), boundBranch(state, operation));
|
|
621
621
|
const accepted = await persist(state, scopeKey, transitionOperation(operation, 'accepted'));
|
|
622
622
|
await recordNativeEvents(state, accepted, [
|
|
623
623
|
{
|
|
@@ -765,6 +765,10 @@ async function executeTerminalCreate(state, scopeKey, operation, payload) {
|
|
|
765
765
|
]);
|
|
766
766
|
return persist(state, scopeKey, transitionOperation(accepted, 'completed', { terminalId, cwd: record.cwd }));
|
|
767
767
|
}
|
|
768
|
+
/** The branch a registered workspace is bound to; the single-workspace mode has none. */
|
|
769
|
+
function boundBranch(state, operation) {
|
|
770
|
+
return operation.workspaceId ? state.journal.getWorkspace(operation.workspaceId)?.branchName : undefined;
|
|
771
|
+
}
|
|
768
772
|
function gitErrorCode(error) {
|
|
769
773
|
return error instanceof GitWorkspaceError ? error.code : 'git_command_failed';
|
|
770
774
|
}
|
|
@@ -171,8 +171,34 @@ function splitLogLine(line) {
|
|
|
171
171
|
return [line.slice(0, first), line.slice(first + 1), ''];
|
|
172
172
|
return [line.slice(0, first), line.slice(first + 1, second), line.slice(second + 1)];
|
|
173
173
|
}
|
|
174
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Commit and push stay on the branch the workspace is bound to. A switch made in a shell or by an
|
|
176
|
+
* agent is reported, never followed, so work cannot land on another Remix.
|
|
177
|
+
*/
|
|
178
|
+
async function assertBoundBranch(rootReal, boundBranch) {
|
|
179
|
+
if (!boundBranch)
|
|
180
|
+
return;
|
|
181
|
+
const current = await gitAllowFail(rootReal, ['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
182
|
+
const branch = current.code === 0 ? current.stdout.trim() : '';
|
|
183
|
+
if (branch !== boundBranch) {
|
|
184
|
+
throw new GitWorkspaceError('workspace_branch_mismatch', branch || 'detached HEAD');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* A push the remote refused for this ref is a definite outcome, unlike a lost response. Porcelain
|
|
189
|
+
* output marks the ref with "!" and says whether the remote has commits this checkout lacks.
|
|
190
|
+
*/
|
|
191
|
+
function pushRejection(stdout) {
|
|
192
|
+
const line = stdout.split('\n').find((item) => item.startsWith('!\t'));
|
|
193
|
+
if (!line)
|
|
194
|
+
return undefined;
|
|
195
|
+
if (/\[rejected\] \((?:non-fast-forward|fetch first)\)/.test(line))
|
|
196
|
+
return 'git_push_remote_ahead';
|
|
197
|
+
return 'git_push_rejected';
|
|
198
|
+
}
|
|
199
|
+
export async function commitWorkspaceGit(root, payload, boundBranch) {
|
|
175
200
|
const rootReal = await ensureWorkspaceGit(root);
|
|
201
|
+
await assertBoundBranch(rootReal, boundBranch);
|
|
176
202
|
if (payload.stagedOnly) {
|
|
177
203
|
// The index is the commit; nothing is added.
|
|
178
204
|
}
|
|
@@ -190,8 +216,9 @@ export async function commitWorkspaceGit(root, payload) {
|
|
|
190
216
|
await git(rootReal, ['commit', '-m', payload.message], 20000);
|
|
191
217
|
return (await git(rootReal, ['rev-parse', 'HEAD'])).trim();
|
|
192
218
|
}
|
|
193
|
-
export async function pushWorkspaceGit(root) {
|
|
219
|
+
export async function pushWorkspaceGit(root, boundBranch) {
|
|
194
220
|
const rootReal = await ensureWorkspaceGit(root);
|
|
221
|
+
await assertBoundBranch(rootReal, boundBranch);
|
|
195
222
|
const origin = await gitAllowFail(rootReal, ['remote', 'get-url', 'origin']);
|
|
196
223
|
if (origin.code !== 0) {
|
|
197
224
|
throw new GitWorkspaceError('remote_not_configured', 'origin');
|
|
@@ -200,12 +227,16 @@ export async function pushWorkspaceGit(root) {
|
|
|
200
227
|
if (!ref.startsWith('refs/heads/'))
|
|
201
228
|
throw new GitWorkspaceError('git_command_failed', 'git_branch_required');
|
|
202
229
|
const commit = (await git(rootReal, ['rev-parse', '--verify', 'HEAD^{commit}'])).trim();
|
|
230
|
+
let pushed;
|
|
203
231
|
try {
|
|
204
|
-
|
|
205
|
-
if (pushed.code === 0)
|
|
206
|
-
return;
|
|
232
|
+
pushed = await gitAllowFail(rootReal, ['push', '--porcelain', 'origin', `${commit}:${ref}`], 90000);
|
|
207
233
|
}
|
|
208
234
|
catch { /* A lost response does not prove the remote rejected the update. */ }
|
|
235
|
+
if (pushed?.code === 0)
|
|
236
|
+
return;
|
|
237
|
+
const rejected = pushed ? pushRejection(pushed.stdout) : undefined;
|
|
238
|
+
if (rejected)
|
|
239
|
+
throw new GitWorkspaceError(rejected, rejected);
|
|
209
240
|
try {
|
|
210
241
|
const remote = await gitAllowFail(rootReal, ['ls-remote', '--refs', 'origin', ref], 20000);
|
|
211
242
|
if (remote.code === 0 && remote.stdout.trim() === `${commit}\t${ref}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcraft/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"@gltf-transform/core": "^4.3.0",
|
|
49
49
|
"@gltf-transform/extensions": "^4.3.0",
|
|
50
50
|
"@gltf-transform/functions": "^4.3.0",
|
|
51
|
-
"@playcraft/build": "^0.1.
|
|
52
|
-
"@playcraft/common": "^0.1.
|
|
51
|
+
"@playcraft/build": "^0.1.8",
|
|
52
|
+
"@playcraft/common": "^0.1.8",
|
|
53
53
|
"better-sqlite3-pinned": "npm:better-sqlite3@13.0.3",
|
|
54
54
|
"chokidar": "^4.0.3",
|
|
55
55
|
"commander": "^13.1.0",
|