@timekast/factory 1.12.0 → 1.14.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/dist/commands/doctor.js +88 -0
- package/dist/commands/new.js +28 -1
- package/dist/commands/provision.js +2487 -0
- package/dist/commands/secrets.js +4 -0
- package/dist/commands/status.js +19 -3
- package/dist/commands/update.js +47 -0
- package/dist/index.js +79 -23
- package/dist/lib/cloudflare-api.js +243 -0
- package/dist/lib/constants.js +11 -0
- package/dist/lib/env-merge.js +40 -3
- package/dist/lib/neon-api.js +342 -0
- package/dist/lib/preflight.js +199 -4
- package/dist/lib/provision-pointer.js +32 -0
- package/dist/lib/provision-preflight.js +342 -0
- package/dist/lib/provision-state.js +385 -0
- package/dist/lib/unpack.js +10 -9
- package/dist/lib/validate-name.js +7 -2
- package/dist/lib/vercel-env.js +490 -0
- package/dist/tui/index.js +8 -0
- package/dist/tui/menu.js +135 -0
- package/dist/tui/provision-flow.js +166 -0
- package/package.json +2 -2
- package/dist/commands/bootstrap-version.js +0 -43
- package/dist/commands/launcher-path.js +0 -31
- package/dist/launcher.applescript +0 -239
package/dist/commands/doctor.js
CHANGED
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
|
+
import { CLIError } from '../lib/cli-error.js';
|
|
21
22
|
import { collectClaudePaths } from '../lib/claude-paths.js';
|
|
22
23
|
import { CLAUDE_MD_FILE } from '../lib/constants.js';
|
|
23
24
|
import { hasLockfile, normalizeThenHash, readLockfile } from '../lib/lockfile.js';
|
|
25
|
+
import { runLauncherPreflight, } from '../lib/preflight.js';
|
|
24
26
|
import { detectRepo } from '../lib/repo-detection.js';
|
|
25
27
|
import { BETA_BANNER } from './status.js';
|
|
26
28
|
/** Matches a flat always-on rule file shipped by the kit (`.claude/rules/<name>.md`). */
|
|
@@ -44,6 +46,13 @@ export const A1_WARNING = 'Aviso de seguridad (A1): el boilerplate `src/` y sus
|
|
|
44
46
|
'dependencia) NO llega por este canal. El canal de parches de `src/` es EPIC 2 ' +
|
|
45
47
|
'(update agentico con merge inteligente), aún no disponible. Mantén `src/` y tus ' +
|
|
46
48
|
'dependencias al día por tu cuenta mientras tanto.';
|
|
49
|
+
/** Parse `doctor`'s argv slice into flags. Unknown flags are ignored. */
|
|
50
|
+
export function parseDoctorFlags(argv) {
|
|
51
|
+
return {
|
|
52
|
+
launcher: argv.includes('--launcher'),
|
|
53
|
+
json: argv.includes('--json'),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
47
56
|
/**
|
|
48
57
|
* Concatenate the body of the reserved "on-demand reference" section of CLAUDE.md
|
|
49
58
|
* — the lines under a heading matching `ON_DEMAND_HEADING_RE`, until the next
|
|
@@ -220,8 +229,87 @@ function renderDoctor(result) {
|
|
|
220
229
|
/**
|
|
221
230
|
* Run `factory doctor`. Always exits 0 — a missing lockfile is advisory, not a
|
|
222
231
|
* failure (DIST-006 §8). The A1 notice is emitted on every run.
|
|
232
|
+
*
|
|
233
|
+
* Synchronous: the standard doctor report has no network/async seam. The
|
|
234
|
+
* additive `--launcher` flow is async (it spawns `gh`) and lives in its own
|
|
235
|
+
* entrypoint, {@link runLauncherDoctor}.
|
|
223
236
|
*/
|
|
224
237
|
export function runDoctor(deps = {}) {
|
|
225
238
|
const result = computeDoctor(deps);
|
|
226
239
|
renderDoctor(result);
|
|
227
240
|
}
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// `factory doctor --launcher` (LTUI-002) — env preflight, any-platform/any-shell
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
/** The glyph per launcher-check status. */
|
|
245
|
+
const STATUS_GLYPH = {
|
|
246
|
+
ok: '✅',
|
|
247
|
+
fail: '❌',
|
|
248
|
+
skipped: '—',
|
|
249
|
+
warn: '🟡',
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Render the launcher preflight report. Each check on its own line with a glyph,
|
|
253
|
+
* label and (for fail/skip) the actionable message. Closes with a one-line
|
|
254
|
+
* verdict. Exported so tests can assert the rendered text.
|
|
255
|
+
*/
|
|
256
|
+
export function renderLauncherDoctor(checks) {
|
|
257
|
+
for (const check of checks) {
|
|
258
|
+
const glyph = STATUS_GLYPH[check.status];
|
|
259
|
+
if (check.status === 'ok') {
|
|
260
|
+
console.log(`${glyph} ${check.label} — ${check.message}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// fail / skipped: surface the actionable message (multi-line indented).
|
|
264
|
+
const lines = check.message.split('\n');
|
|
265
|
+
console.log(`${glyph} ${check.label}: ${lines[0]}`);
|
|
266
|
+
for (const extra of lines.slice(1)) {
|
|
267
|
+
if (extra.trim())
|
|
268
|
+
console.log(` ${extra}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const hasFail = checks.some((c) => c.status === 'fail');
|
|
273
|
+
const hasWarn = checks.some((c) => c.status === 'warn');
|
|
274
|
+
console.log('');
|
|
275
|
+
if (hasFail) {
|
|
276
|
+
console.log('Entorno incompleto: corrige los checks marcados ❌ y vuelve a intentar.');
|
|
277
|
+
}
|
|
278
|
+
else if (hasWarn) {
|
|
279
|
+
console.log('Entorno listo. Revisa los avisos 🟡 (no bloquean el launcher).');
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
console.log('Entorno listo para usar el launcher.');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Run `factory doctor --launcher`: the cross-platform environment preflight
|
|
287
|
+
* (LTUI-002). Renders the four checks in order (Node ≥ 22 → gh → auth → org)
|
|
288
|
+
* with ✅ / ❌, then exits 0 if all pass or throws a `CLIError` (exit 1) if any
|
|
289
|
+
* fails — so a CI step or the TUI (LTUI-001) can branch on the exit code.
|
|
290
|
+
*
|
|
291
|
+
* In `--json` mode (TGUI-003) the `LauncherCheck[]` is written as a JSON array
|
|
292
|
+
* to stdout and NOTHING else (the human renderer is suppressed so stdout stays
|
|
293
|
+
* pure-parseable JSON). It ALWAYS exits 0 — a failed check is a `status: 'fail'`
|
|
294
|
+
* element the GUI reads, never a non-zero exit; the exit-1 contract is preserved
|
|
295
|
+
* only for the interactive / CI path (no `--json`). `runLauncherPreflight` is
|
|
296
|
+
* non-fail-fast (returns the full report instead of throwing), so the JSON path
|
|
297
|
+
* is fail-open by construction.
|
|
298
|
+
*
|
|
299
|
+
* `deps` is the same injectable seam as `runLauncherPreflight` (tests drive the
|
|
300
|
+
* Node version + the gh steps without spawning sub-processes).
|
|
301
|
+
*/
|
|
302
|
+
export async function runLauncherDoctor(deps = {}, json = false) {
|
|
303
|
+
const checks = await runLauncherPreflight(deps);
|
|
304
|
+
if (json) {
|
|
305
|
+
process.stdout.write(`${JSON.stringify(checks)}\n`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
renderLauncherDoctor(checks);
|
|
309
|
+
if (checks.some((c) => c.status === 'fail')) {
|
|
310
|
+
// Non-zero exit without a duplicate error dump: the per-check ❌ lines above
|
|
311
|
+
// already told the user what to fix. The empty message keeps the top-level
|
|
312
|
+
// handler from printing a second "✖ ..." block.
|
|
313
|
+
throw new CLIError('', 1);
|
|
314
|
+
}
|
|
315
|
+
}
|
package/dist/commands/new.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* and is non-empty → preflight (gh) → choose profile (full | core) → create the
|
|
6
6
|
* GitHub repo → download the profile tarball → unpack into `./<name>/` (no
|
|
7
7
|
* Factory `.git/`) → `git init` → rename `package.json.name` → write the initial
|
|
8
|
-
* lockfile → inject `factory:update` → initial commit
|
|
8
|
+
* lockfile → inject `factory:update` → initial commit → wire `origin` + push `main`
|
|
9
|
+
* (the last two non-fatal if no git id / no network; provision re-establishes them).
|
|
9
10
|
*
|
|
10
11
|
* Expected failures throw `CLIError`; the top-level handler prints the message
|
|
11
12
|
* and exits with the carried code.
|
|
@@ -18,6 +19,7 @@ import prompts from 'prompts';
|
|
|
18
19
|
import { CLIError } from '../lib/cli-error.js';
|
|
19
20
|
import { FACTORY_ORG, PROFILES } from '../lib/constants.js';
|
|
20
21
|
import { parseLockfile, writeInitialLockfile } from '../lib/lockfile.js';
|
|
22
|
+
import { printProvisionPointer } from '../lib/provision-pointer.js';
|
|
21
23
|
import { applyPackageJsonEdits, kitLocalScripts } from '../lib/package-json.js';
|
|
22
24
|
import { renderDerivedReadme } from '../lib/readme.js';
|
|
23
25
|
import { runPreflight } from '../lib/preflight.js';
|
|
@@ -135,16 +137,37 @@ export async function runNew(name, flags = { core: false, full: false }) {
|
|
|
135
137
|
writeInitialLockfile(destDir, manifestRaw);
|
|
136
138
|
// Initial commit (H3). Non-fatal: a fresh machine may lack git identity —
|
|
137
139
|
// the project is already usable; warn and let the dev commit by hand (B2).
|
|
140
|
+
let committed = false;
|
|
138
141
|
try {
|
|
139
142
|
await execa('git', ['add', '-A'], { cwd: destDir });
|
|
140
143
|
await execa('git', ['commit', '-m', 'chore: initial commit from TimeKast Factory'], {
|
|
141
144
|
cwd: destDir,
|
|
142
145
|
});
|
|
146
|
+
committed = true;
|
|
143
147
|
}
|
|
144
148
|
catch {
|
|
145
149
|
console.warn('Aviso: no se pudo crear el commit inicial (¿falta `git config --global user.email`/`user.name`?). ' +
|
|
146
150
|
'El proyecto está listo; commitea a mano cuando configures tu identidad git.');
|
|
147
151
|
}
|
|
152
|
+
// First push: name the branch `main`, wire the `origin` remote (the repo
|
|
153
|
+
// `gh repo create` made is empty until now), and push. This is what lets the
|
|
154
|
+
// DOCS pipeline (discovery/design/backlog) commit + push BEFORE provision —
|
|
155
|
+
// `provision` later creates + pushes the `develop` branch when infra is set up.
|
|
156
|
+
// Non-fatal: no network / no auth → the local project is ready and `provision`
|
|
157
|
+
// re-establishes the substrate. Only attempted when the commit landed.
|
|
158
|
+
if (committed) {
|
|
159
|
+
try {
|
|
160
|
+
await execa('git', ['branch', '-M', 'main'], { cwd: destDir });
|
|
161
|
+
await execa('git', ['remote', 'add', 'origin', `https://github.com/${FACTORY_ORG}/${validName}.git`], {
|
|
162
|
+
cwd: destDir,
|
|
163
|
+
});
|
|
164
|
+
await execa('git', ['push', '-u', 'origin', 'main'], { cwd: destDir });
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
console.warn('Aviso: no se pudo pushear `main` al repo (¿sin red o sin permiso?). ' +
|
|
168
|
+
'El repo local está listo; `factory provision` reestablece el remote y el push.');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
148
171
|
}
|
|
149
172
|
finally {
|
|
150
173
|
process.removeListener('SIGINT', onSignal);
|
|
@@ -154,4 +177,8 @@ export async function runNew(name, flags = { core: false, full: false }) {
|
|
|
154
177
|
console.log(`\n✔ Proyecto \`${validName}\` listo (perfil \`${profile}\`, cerebro v${version}). ` +
|
|
155
178
|
`Repo: ${FACTORY_ORG}/${validName}`);
|
|
156
179
|
console.log(` cd ${validName}`);
|
|
180
|
+
// End-pointer to the next step (PVSKILL-002). Only reached on a successful run —
|
|
181
|
+
// an earlier throw (CLIError / Ctrl-C) never gets here. `new` has no `--yes` flag,
|
|
182
|
+
// so this always prints in a normal run (a pure pipe is still informative).
|
|
183
|
+
printProvisionPointer();
|
|
157
184
|
}
|