@celilo/cli 0.16.1 → 0.16.2
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celilo/cli",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "Celilo — home lab orchestration CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@celilo/capabilities": "^0.9.1",
|
|
61
61
|
"@celilo/cli-display": "^0.1.9",
|
|
62
62
|
"@celilo/core": "^0.3.0",
|
|
63
|
-
"@celilo/event-bus": "^0.1.
|
|
63
|
+
"@celilo/event-bus": "^0.1.10",
|
|
64
64
|
"@clack/prompts": "^1.1.0",
|
|
65
65
|
"ajv": "^8.18.0",
|
|
66
66
|
"drizzle-orm": "^0.36.4",
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
type GitCommandRunner,
|
|
8
|
+
buildReleaseMetadata,
|
|
9
|
+
collectGitInfo,
|
|
10
|
+
makeRealGitRunner,
|
|
11
|
+
} from './release-metadata';
|
|
3
12
|
|
|
4
13
|
describe('buildReleaseMetadata', () => {
|
|
5
14
|
test('produces a stable shape from injected inputs', () => {
|
|
@@ -77,7 +86,7 @@ describe('collectGitInfo', () => {
|
|
|
77
86
|
expect(calls).toEqual([
|
|
78
87
|
['rev-parse', 'HEAD'],
|
|
79
88
|
['rev-parse', '--abbrev-ref', 'HEAD'],
|
|
80
|
-
['status', '--porcelain'],
|
|
89
|
+
['status', '--porcelain', '--', '.'],
|
|
81
90
|
]);
|
|
82
91
|
});
|
|
83
92
|
|
|
@@ -101,3 +110,68 @@ describe('collectGitInfo', () => {
|
|
|
101
110
|
expect(collectGitInfo('/tmp/x', run).dirty).toBe(true);
|
|
102
111
|
});
|
|
103
112
|
});
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Recurrence gate for #544, against a REAL git repo rather than a fake runner.
|
|
116
|
+
*
|
|
117
|
+
* The tests above pin the argv `collectGitInfo` constructs. That is what let the
|
|
118
|
+
* bug live: they asserted which command was built, never what it concluded, so
|
|
119
|
+
* they stayed green while the function reported the whole repository's dirt for
|
|
120
|
+
* every module. A fake `GitCommandRunner` cannot catch this at all — the defect
|
|
121
|
+
* IS git's real scoping behavior, which a stub by definition does not model.
|
|
122
|
+
*
|
|
123
|
+
* So this drives the real `makeRealGitRunner()` against a real checkout. Delete
|
|
124
|
+
* the `-- .` pathspec and the first test here fails; that is the whole point.
|
|
125
|
+
*/
|
|
126
|
+
describe('collectGitInfo scopes dirt to sourceDir (#544, real git)', () => {
|
|
127
|
+
let repo: string;
|
|
128
|
+
let moduleDir: string;
|
|
129
|
+
const git = (args: string[], cwd: string) =>
|
|
130
|
+
execFileSync('git', args, { cwd, encoding: 'utf-8' });
|
|
131
|
+
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
repo = mkdtempSync(join(tmpdir(), 'celilo-gitinfo-'));
|
|
134
|
+
moduleDir = join(repo, 'modules', 'probe');
|
|
135
|
+
mkdirSync(moduleDir, { recursive: true });
|
|
136
|
+
writeFileSync(join(moduleDir, 'manifest.yml'), 'id: probe\n');
|
|
137
|
+
writeFileSync(join(repo, 'bun.lock'), 'lockfile v1\n');
|
|
138
|
+
|
|
139
|
+
git(['init', '-q'], repo);
|
|
140
|
+
git(['config', 'user.email', 'test@celilo.test'], repo);
|
|
141
|
+
git(['config', 'user.name', 'Test'], repo);
|
|
142
|
+
git(['add', '-A'], repo);
|
|
143
|
+
git(['commit', '-qm', 'init'], repo);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
afterEach(() => rmSync(repo, { recursive: true, force: true }));
|
|
147
|
+
|
|
148
|
+
test('a tracked file rewritten OUTSIDE the module does not make it dirty', () => {
|
|
149
|
+
// Exactly what broke the release: `bun install` rewrites the tracked root
|
|
150
|
+
// `bun.lock` between module publishes. The module itself is untouched.
|
|
151
|
+
writeFileSync(join(repo, 'bun.lock'), 'lockfile v1\nrewritten by bun install\n');
|
|
152
|
+
|
|
153
|
+
expect(collectGitInfo(moduleDir, makeRealGitRunner()).dirty).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("a sibling module's dirt does not make this module dirty", () => {
|
|
157
|
+
const sibling = join(repo, 'modules', 'other');
|
|
158
|
+
mkdirSync(sibling, { recursive: true });
|
|
159
|
+
writeFileSync(join(sibling, 'manifest.yml'), 'id: other\n');
|
|
160
|
+
|
|
161
|
+
expect(collectGitInfo(moduleDir, makeRealGitRunner()).dirty).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("the module's OWN dirt still blocks the publish", () => {
|
|
165
|
+
// The control. Scoping must not have simply disabled the check — this is
|
|
166
|
+
// the case the guard exists for, and it must still fire.
|
|
167
|
+
writeFileSync(join(moduleDir, 'manifest.yml'), 'id: probe\nversion: 9.9.9\n');
|
|
168
|
+
|
|
169
|
+
expect(collectGitInfo(moduleDir, makeRealGitRunner()).dirty).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('an untracked file inside the module still blocks the publish', () => {
|
|
173
|
+
writeFileSync(join(moduleDir, 'stray.txt'), 'oops\n');
|
|
174
|
+
|
|
175
|
+
expect(collectGitInfo(moduleDir, makeRealGitRunner()).dirty).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -129,7 +129,17 @@ export function collectGitInfo(sourceDir: string, run: GitCommandRunner): GitInf
|
|
|
129
129
|
// `git status --porcelain` prints one line per modified/untracked file.
|
|
130
130
|
// Empty output = clean. Null (command failure) is treated as not-dirty
|
|
131
131
|
// because we don't want to falsely block a publish.
|
|
132
|
-
|
|
132
|
+
//
|
|
133
|
+
// The `-- .` pathspec is load-bearing (#544). `git status` reports the WHOLE
|
|
134
|
+
// repository regardless of cwd, so passing `sourceDir` as cwd scoped nothing:
|
|
135
|
+
// every caller asks about one module, and got back the dirt of all of them
|
|
136
|
+
// plus the repo root. The release pipeline runs `bun install` between module
|
|
137
|
+
// publishes, which rewrites the tracked `bun.lock` at the root — so modules
|
|
138
|
+
// 1..N published fine and the next one failed with "Working tree at
|
|
139
|
+
// modules/<clean-module> has uncommitted changes", naming a directory that
|
|
140
|
+
// was clean and sending you to inspect it. Order-dependent, so it looked
|
|
141
|
+
// like a random module failing.
|
|
142
|
+
const status = run(['status', '--porcelain', '--', '.'], sourceDir);
|
|
133
143
|
const dirty = status !== null && status.length > 0;
|
|
134
144
|
|
|
135
145
|
return {
|