@timekast/factory 2.1.0 → 2.2.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/dist/commands/ticket.js +17 -48
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/commands/ticket.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* A derivative documents a kit defect as a markdown file under `project/factory/`
|
|
5
5
|
* (the `fx-factory-tickets` convention). Until now that file was a dead end: it sat
|
|
6
6
|
* in the repo until somebody opened the Factory by hand. This command delivers it as
|
|
7
|
-
* a GitHub issue in the Factory repo and
|
|
8
|
-
*
|
|
7
|
+
* a GitHub issue in the Factory repo and then DELETES the local file: it was only a
|
|
8
|
+
* draft, and from here on the issue is the one source of state (open / closed,
|
|
9
|
+
* labels). A local copy with its own status only drifted from GitHub, and a second
|
|
10
|
+
* `push` of it opened a duplicate issue (`fx-factory-tickets §6`).
|
|
9
11
|
*
|
|
10
12
|
* THREE THINGS THIS COMMAND DELIBERATELY DOES NOT DO:
|
|
11
13
|
*
|
|
@@ -34,7 +36,7 @@
|
|
|
34
36
|
* preflight degrades to a note + `exit 0` — the same `not-configured` contract
|
|
35
37
|
* `factory backlog push` has without `BACKLOG_API_KEY`, never a stack trace.
|
|
36
38
|
*/
|
|
37
|
-
import { existsSync, readFileSync, realpathSync, statSync,
|
|
39
|
+
import { existsSync, readFileSync, realpathSync, statSync, unlinkSync } from 'node:fs';
|
|
38
40
|
import path from 'node:path';
|
|
39
41
|
import { execa } from 'execa';
|
|
40
42
|
import prompts from 'prompts';
|
|
@@ -163,36 +165,6 @@ export function resolveTicketPath(rootDir, filePath) {
|
|
|
163
165
|
return realTarget;
|
|
164
166
|
}
|
|
165
167
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
-
// Stamping (`fx-factory-tickets §6.2`)
|
|
167
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
168
|
-
/** Canonical header field the delivered URL is stamped into. It is the ONLY field a tool writes. */
|
|
169
|
-
const GITHUB_ISSUE_FIELD = '**GitHub issue:**';
|
|
170
|
-
const ESTADO_FIELD = '**Estado:**';
|
|
171
|
-
/**
|
|
172
|
-
* Write `url` into the ticket's `**GitHub issue:**` slot, which is born holding an
|
|
173
|
-
* em-dash and sits immediately after `**Estado:**`. Stamping the URL does NOT touch
|
|
174
|
-
* `Estado` — delivering is not triaging (`fx-factory-tickets §6.2`).
|
|
175
|
-
*
|
|
176
|
-
* A ticket written before the convention has no such field; it is inserted right
|
|
177
|
-
* after `Estado` rather than dropped, so the ticket stops being a dead end either
|
|
178
|
-
* way. With no header block at all, nothing is changed and the caller warns.
|
|
179
|
-
*/
|
|
180
|
-
export function stampGitHubIssueUrl(content, url) {
|
|
181
|
-
const stamped = `${GITHUB_ISSUE_FIELD} ${url}`;
|
|
182
|
-
const lines = content.split('\n');
|
|
183
|
-
const fieldIdx = lines.findIndex((line) => line.startsWith(GITHUB_ISSUE_FIELD));
|
|
184
|
-
if (fieldIdx >= 0) {
|
|
185
|
-
lines[fieldIdx] = stamped;
|
|
186
|
-
return { content: lines.join('\n'), changed: true };
|
|
187
|
-
}
|
|
188
|
-
const estadoIdx = lines.findIndex((line) => line.startsWith(ESTADO_FIELD));
|
|
189
|
-
if (estadoIdx >= 0) {
|
|
190
|
-
lines.splice(estadoIdx + 1, 0, stamped);
|
|
191
|
-
return { content: lines.join('\n'), changed: true };
|
|
192
|
-
}
|
|
193
|
-
return { content, changed: false };
|
|
194
|
-
}
|
|
195
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
196
168
|
// Terminal safety
|
|
197
169
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
198
170
|
/**
|
|
@@ -271,7 +243,7 @@ async function defaultConfirm(message) {
|
|
|
271
243
|
*
|
|
272
244
|
* Order matters and is asserted by the unit test: containment → read → size →
|
|
273
245
|
* (`--dry-run` stops here) → gh session → publish gate → create → verify
|
|
274
|
-
* assignee/label →
|
|
246
|
+
* assignee/label → delete the draft. Every guard that can reject runs BEFORE `gh` is ever
|
|
275
247
|
* spawned.
|
|
276
248
|
*/
|
|
277
249
|
export async function runTicketPush(filePath, flags = parseTicketPushFlags([]), rootDir = detectRepo(process.cwd()).repoRoot ?? process.cwd(), deps = {}) {
|
|
@@ -387,8 +359,8 @@ export async function runTicketPush(filePath, flags = parseTicketPushFlags([]),
|
|
|
387
359
|
if (!url) {
|
|
388
360
|
console.log(`✅ Issue creado en ${FACTORY_REPO}.`);
|
|
389
361
|
console.log(stdout.trim());
|
|
390
|
-
console.warn('⚠️ No se pudo leer la URL de la salida de `gh`, así que el
|
|
391
|
-
'
|
|
362
|
+
console.warn('⚠️ No se pudo leer la URL de la salida de `gh`, así que el borrador local NO se borró. ' +
|
|
363
|
+
'Revisa el issue de arriba y borra el archivo a mano (no lo vuelvas a entregar: abriría otro issue).');
|
|
392
364
|
return;
|
|
393
365
|
}
|
|
394
366
|
console.log(`✅ Issue creado: ${url}`);
|
|
@@ -407,22 +379,19 @@ export async function runTicketPush(filePath, flags = parseTicketPushFlags([]),
|
|
|
407
379
|
// permission is `read` — so the two notification mechanisms of this channel can
|
|
408
380
|
// fail at once, without a word, unless we look.
|
|
409
381
|
await verifyIssueFields(url, flags, labelDropped, assigneeDropped);
|
|
410
|
-
// 6.
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
console.warn(`⚠️ \`${shown}\` no tiene el campo \`${GITHUB_ISSUE_FIELD}\` ni \`${ESTADO_FIELD}\`, así que no se estampó. ` +
|
|
415
|
-
'Agrega la URL a mano (`fx-factory-tickets §6.2`).');
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
382
|
+
// 6. The draft is delivered: delete it. From here the issue is the only source of
|
|
383
|
+
// state. If this fails, the URL is already on screen (step above) — never leave
|
|
384
|
+
// the dev without the URL of an issue that already exists, and never fail a
|
|
385
|
+
// delivery that succeeded.
|
|
418
386
|
try {
|
|
419
|
-
(deps.
|
|
420
|
-
console.log(`
|
|
387
|
+
(deps.removeFile ?? unlinkSync)(target);
|
|
388
|
+
console.log(` Borré el borrador \`${shown}\`: el estado vive en el issue. ` +
|
|
389
|
+
'Commitea el borrado (`docs(factory): …`).');
|
|
421
390
|
}
|
|
422
391
|
catch (error) {
|
|
423
|
-
console.warn(`⚠️ El issue existe (${url}) pero \`${shown}\` no se pudo
|
|
392
|
+
console.warn(`⚠️ El issue existe (${url}) pero \`${shown}\` no se pudo borrar: ` +
|
|
424
393
|
`${error instanceof Error ? error.message : String(error)}\n` +
|
|
425
|
-
|
|
394
|
+
' Bórralo a mano; entregarlo otra vez abriría un issue duplicado.');
|
|
426
395
|
}
|
|
427
396
|
}
|
|
428
397
|
/**
|
package/dist/index.js
CHANGED
|
@@ -191,8 +191,8 @@ Sobre \`backlog\`:
|
|
|
191
191
|
|
|
192
192
|
Sobre \`ticket\`:
|
|
193
193
|
\`ticket push <archivo>\` entrega un factory-ticket de \`project/factory/\` al repo
|
|
194
|
-
del Factory como un issue de GitHub
|
|
195
|
-
|
|
194
|
+
del Factory como un issue de GitHub y, entregado, BORRA el archivo local: era un
|
|
195
|
+
borrador, y el estado vive en el issue (commitea el borrado con \`docs(factory): …\`).
|
|
196
196
|
Cero credenciales nuevas: usa tu propia sesión de \`gh\`; sin sesión avisa y sale 0.
|
|
197
197
|
El cuerpo se publica ÍNTEGRO en un repo compartido, así que hay compuerta:
|
|
198
198
|
--dry-run Imprime destino, asignado, etiqueta y título; no crea nada
|