@programinglive/dev-workflow-mcp-server 1.13.2 → 1.13.4
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/exec.js +23 -7
- package/package.json +1 -1
- package/scripts/release.cjs +126 -7
package/exec.js
CHANGED
|
@@ -52,8 +52,16 @@ function samePath(a, b) {
|
|
|
52
52
|
* Menentukan direktori repo proyek tempat git boleh dijalankan.
|
|
53
53
|
*
|
|
54
54
|
* Urutan: eksplisit -> WORKFLOW_PROJECT_DIR/PROJECT_DIR -> kandidat.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
55
|
+
*
|
|
56
|
+
* Repo paket MCP sengaja tidak dipilih sebagai cadangan: dulu server pernah
|
|
57
|
+
* menjalankan git di repo-nya sendiri saat cwd proyek gagal diresolusi, lalu
|
|
58
|
+
* melaporkan "No changes detected" sementara perubahan proyek tidak pernah
|
|
59
|
+
* ter-commit.
|
|
60
|
+
*
|
|
61
|
+
* Tetapi kandidat yang MEMANG berada di dalam repo paket berarti pemanggil
|
|
62
|
+
* sengaja bekerja di sana - misalnya saat mengembangkan server MCP ini sendiri.
|
|
63
|
+
* Membuangnya tanpa syarat membuat `commit_and_push` mustahil dipakai di repo
|
|
64
|
+
* paket, jadi kandidat seperti itu diterima. Repo proyek lain tetap menang.
|
|
57
65
|
*/
|
|
58
66
|
export function resolveWorkingDirectory({ explicit = "", env = process.env, candidates = [] } = {}) {
|
|
59
67
|
if (explicit) {
|
|
@@ -74,14 +82,22 @@ export function resolveWorkingDirectory({ explicit = "", env = process.env, cand
|
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
const found = candidates.map((candidate) => findRepoRoot(candidate)).filter(Boolean);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
|
|
86
|
+
const projectRepo = found.find((dir) => !samePath(dir, PACKAGE_DIR));
|
|
87
|
+
if (projectRepo) {
|
|
88
|
+
return projectRepo;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Hanya repo paket yang ditemukan. Itu hanya mungkin bila salah satu kandidat
|
|
92
|
+
// benar-benar berada di dalamnya, jadi pemanggil memilih repo ini dengan sadar.
|
|
93
|
+
if (found.some((dir) => samePath(dir, PACKAGE_DIR))) {
|
|
94
|
+
return path.resolve(PACKAGE_DIR);
|
|
80
95
|
}
|
|
81
96
|
|
|
82
97
|
throw new Error(
|
|
83
|
-
"Tidak bisa menentukan repo proyek:
|
|
84
|
-
|
|
98
|
+
"Tidak bisa menentukan repo proyek: tidak ada repository git pada " +
|
|
99
|
+
`${candidates.filter(Boolean).map((candidate) => `"${candidate}"`).join(", ") || "direktori kerja"}. ` +
|
|
100
|
+
"Set WORKFLOW_PROJECT_DIR ke direktori proyek, atau jalankan server MCP dari dalam direktori proyek.",
|
|
85
101
|
);
|
|
86
102
|
}
|
|
87
103
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.4",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"mcpName": "io.github.programinglive/dev-workflow-mcp-server",
|
package/scripts/release.cjs
CHANGED
|
@@ -40,6 +40,40 @@ function getCliArguments(argv = process.argv) {
|
|
|
40
40
|
return { releaseType: 'patch', extraArgs: [] };
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// `--release-as` selalu diambil sebagai tipe rilis dan DIBUANG dari extraArgs.
|
|
44
|
+
//
|
|
45
|
+
// Kalau dibiarkan, argumen itu sampai ke standard-version dua kali: sekali
|
|
46
|
+
// dari releaseType, sekali dari extraArgs. yargs lalu mengubahnya menjadi
|
|
47
|
+
// array, dan `semver.inc("1.13.2", ["patch","patch"])` mengembalikan null -
|
|
48
|
+
// rilis menulis `"version": null` dan tag `vnull`.
|
|
49
|
+
const releaseAsIndex = rawArgs.indexOf('--release-as');
|
|
50
|
+
if (releaseAsIndex !== -1) {
|
|
51
|
+
const value = rawArgs[releaseAsIndex + 1];
|
|
52
|
+
if (!value || value.startsWith('-')) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'--release-as memerlukan nilai tipe rilis (major, minor, patch, atau versi semver).'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
releaseType: value,
|
|
59
|
+
extraArgs: [...rawArgs.slice(0, releaseAsIndex), ...rawArgs.slice(releaseAsIndex + 2)]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const inline = rawArgs.find((arg) => arg.startsWith('--release-as='));
|
|
64
|
+
if (inline) {
|
|
65
|
+
const value = inline.slice('--release-as='.length);
|
|
66
|
+
if (!value) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'--release-as memerlukan nilai tipe rilis (major, minor, patch, atau versi semver).'
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
releaseType: value,
|
|
73
|
+
extraArgs: rawArgs.filter((arg) => arg !== inline)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
43
77
|
if (rawArgs[0].startsWith('-')) {
|
|
44
78
|
return { releaseType: 'patch', extraArgs: rawArgs };
|
|
45
79
|
}
|
|
@@ -62,17 +96,43 @@ function getNpmRunArgument(env = process.env) {
|
|
|
62
96
|
return undefined;
|
|
63
97
|
}
|
|
64
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Membuang setiap `--release-as` dari argumen tambahan.
|
|
101
|
+
*
|
|
102
|
+
* Pertahanan lapis kedua: tipe rilis sudah ditentukan lewat `releaseType`, jadi
|
|
103
|
+
* salinan apa pun di `extraArgs` hanya akan menggandakan opsi dan membuat yargs
|
|
104
|
+
* memberinya array - yang berakhir pada `semver.inc(...) === null`.
|
|
105
|
+
*/
|
|
106
|
+
function stripReleaseAs(extraArgs) {
|
|
107
|
+
if (!Array.isArray(extraArgs)) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const kept = [];
|
|
112
|
+
for (let index = 0; index < extraArgs.length; index += 1) {
|
|
113
|
+
const arg = extraArgs[index];
|
|
114
|
+
if (arg === '--release-as') {
|
|
115
|
+
index += 1; // lewati nilainya juga
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (typeof arg === 'string' && arg.startsWith('--release-as=')) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
kept.push(arg);
|
|
122
|
+
}
|
|
123
|
+
return kept;
|
|
124
|
+
}
|
|
125
|
+
|
|
65
126
|
function buildStandardVersionArgs({ releaseType, extraArgs }) {
|
|
66
127
|
const args = [];
|
|
67
|
-
|
|
128
|
+
const remainingArgs = stripReleaseAs(extraArgs);
|
|
129
|
+
|
|
68
130
|
// Handle --first-release flag
|
|
69
|
-
const isFirstRelease =
|
|
131
|
+
const isFirstRelease = remainingArgs.includes('--first-release');
|
|
70
132
|
if (isFirstRelease) {
|
|
71
133
|
args.push('--release-as', '0.0.1');
|
|
72
|
-
// Remove --first-release from extraArgs to avoid passing it to standard-version
|
|
73
|
-
extraArgs = extraArgs.filter(arg => arg !== '--first-release');
|
|
74
134
|
} else if (releaseType) {
|
|
75
|
-
const normalized = releaseType.trim();
|
|
135
|
+
const normalized = String(releaseType).trim();
|
|
76
136
|
const isValid = VALID_RELEASE_TYPES.has(normalized) || SEMVER_REGEX.test(normalized);
|
|
77
137
|
if (!isValid) {
|
|
78
138
|
const allowed = Array.from(VALID_RELEASE_TYPES).join(', ');
|
|
@@ -81,8 +141,8 @@ function buildStandardVersionArgs({ releaseType, extraArgs }) {
|
|
|
81
141
|
args.push('--release-as', normalized);
|
|
82
142
|
}
|
|
83
143
|
|
|
84
|
-
if (
|
|
85
|
-
args.push(...
|
|
144
|
+
if (remainingArgs.length > 0) {
|
|
145
|
+
args.push(...remainingArgs.filter((arg) => arg !== '--first-release'));
|
|
86
146
|
}
|
|
87
147
|
|
|
88
148
|
return args;
|
|
@@ -98,6 +158,52 @@ function loadPackageJson(cwd = process.cwd()) {
|
|
|
98
158
|
}
|
|
99
159
|
}
|
|
100
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Memastikan package.json berisi versi semver yang sah SETELAH rilis berjalan.
|
|
163
|
+
*
|
|
164
|
+
* MENGAPA ADA
|
|
165
|
+
* -----------
|
|
166
|
+
* 30 Oktober 2026 `perform_release` menulis `"version": null` ke package.json
|
|
167
|
+
* dan membuat tag `vnull`, lalu mem-push keduanya. Penyebabnya argumen
|
|
168
|
+
* `--release-as` terkirim dua kali sehingga `semver.inc()` mengembalikan null.
|
|
169
|
+
*
|
|
170
|
+
* `SEMVER_REGEX` sudah ada saat itu, tetapi hanya dipakai memvalidasi tipe
|
|
171
|
+
* rilis MASUKAN. Hasil rilis tidak pernah diperiksa, jadi kerusakan baru
|
|
172
|
+
* terlihat setelah tag keluar dari mesin.
|
|
173
|
+
*
|
|
174
|
+
* Fungsi ini menutup celah itu: standard-version dianggap belum berhasil
|
|
175
|
+
* sebelum package.json terbukti berisi semver yang sah.
|
|
176
|
+
*/
|
|
177
|
+
function verifyReleaseVersion({ cwd = process.cwd(), readFileSync = fs.readFileSync } = {}) {
|
|
178
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
179
|
+
|
|
180
|
+
let contents;
|
|
181
|
+
try {
|
|
182
|
+
contents = readFileSync(packageJsonPath, 'utf8');
|
|
183
|
+
} catch (error) {
|
|
184
|
+
throw new Error(`Tidak bisa membaca ${packageJsonPath}; hasil rilis tidak dapat diverifikasi.`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let parsed;
|
|
188
|
+
try {
|
|
189
|
+
parsed = JSON.parse(contents);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
throw new Error(`${packageJsonPath} bukan JSON yang sah setelah rilis; hasilnya tidak dapat diverifikasi.`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const version = parsed?.version;
|
|
195
|
+
|
|
196
|
+
if (typeof version !== 'string' || !SEMVER_REGEX.test(version.trim())) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Rilis gagal: package.json berisi version ${JSON.stringify(version)}, bukan semver yang sah. ` +
|
|
199
|
+
'Tag tidak boleh di-push. Periksa argumen --release-as, lalu pulihkan dengan ' +
|
|
200
|
+
`"git tag -d v${String(version)}" dan "git reset --hard HEAD~1" bila perlu.`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return version.trim();
|
|
205
|
+
}
|
|
206
|
+
|
|
101
207
|
function detectPackageManager({ env = process.env, cwd = process.cwd() } = {}) {
|
|
102
208
|
const userAgent = env.npm_config_user_agent || '';
|
|
103
209
|
if (userAgent.startsWith('pnpm/')) return 'pnpm';
|
|
@@ -189,6 +295,18 @@ function runRelease({
|
|
|
189
295
|
return { ...releaseResult, status: releaseResult?.status || 1 };
|
|
190
296
|
}
|
|
191
297
|
|
|
298
|
+
// Rilis hanya dianggap berhasil setelah package.json terbukti berisi semver
|
|
299
|
+
// yang sah. Tanpa pemeriksaan ini, `version: null` dan tag `vnull` lolos
|
|
300
|
+
// sampai di-push (kejadian 30 Oktober 2026).
|
|
301
|
+
let releasedVersion;
|
|
302
|
+
try {
|
|
303
|
+
releasedVersion = verifyReleaseVersion({ cwd });
|
|
304
|
+
} catch (error) {
|
|
305
|
+
safeLog('error', error.message);
|
|
306
|
+
return { status: 1, error };
|
|
307
|
+
}
|
|
308
|
+
safeLog('info', `✅ Released ${releasedVersion}`);
|
|
309
|
+
|
|
192
310
|
const releaseNotesPath = dependencies.releaseNotesPath || path.join('docs', 'release-notes', 'RELEASE_NOTES.md');
|
|
193
311
|
const updateNotes = dependencies.updateReleaseNotes || ((options = {}) => updateReleaseNotes({ rootDir: cwd, ...options }));
|
|
194
312
|
|
|
@@ -224,6 +342,7 @@ module.exports = {
|
|
|
224
342
|
getNpmRunArgument,
|
|
225
343
|
buildStandardVersionArgs,
|
|
226
344
|
loadPackageJson,
|
|
345
|
+
verifyReleaseVersion,
|
|
227
346
|
detectPackageManager,
|
|
228
347
|
runProjectTests,
|
|
229
348
|
runRelease,
|