@timekast/factory 1.23.1 → 1.25.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/doctor.js +39 -0
- package/dist/commands/provision.js +561 -51
- package/dist/commands/update.js +11 -0
- package/dist/lib/initial-migration.js +131 -0
- package/dist/lib/neon-api.js +45 -14
- package/dist/lib/retrofit-reminder.js +228 -0
- package/dist/lib/vercel-env.js +99 -10
- package/package.json +1 -1
package/dist/commands/doctor.js
CHANGED
|
@@ -12,15 +12,26 @@
|
|
|
12
12
|
* `src/` + boilerplate deps are the derived project's responsibility
|
|
13
13
|
* post-bootstrap, and that the patch channel for `src/` is EPIC 2 (agentic
|
|
14
14
|
* update, not yet available).
|
|
15
|
+
* - MISSING INITIAL MIGRATION: a derivative that ships a Drizzle schema and no
|
|
16
|
+
* generated `.sql`, so its `vercel-build` (`pnpm db:migrate && pnpm build`)
|
|
17
|
+
* would migrate an empty database. INFORMATIONAL — see below.
|
|
15
18
|
*
|
|
16
19
|
* No lockfile (pre-CLI repo) → delegate to the "run factory:update" advice, exit
|
|
17
20
|
* 0, never abort (same pattern as `status` / the §8 edge case).
|
|
21
|
+
*
|
|
22
|
+
* 🔴 EVERY CHECK HERE IS ADVISORY. `runDoctor` always exits 0 by contract; the
|
|
23
|
+
* sibling entrypoint {@link runLauncherDoctor} is the one that throws `CLIError(1)`
|
|
24
|
+
* on any `status: 'fail'`. A report-only finding therefore belongs in
|
|
25
|
+
* `computeDoctor`/`renderDoctor` and NOWHERE ELSE — moving it to the launcher would
|
|
26
|
+
* turn a notice into a blocked command through the back door. If it ever has to
|
|
27
|
+
* live there, it goes as `warn` (`LauncherCheckStatus`), which never trips the exit.
|
|
18
28
|
*/
|
|
19
29
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
20
30
|
import path from 'node:path';
|
|
21
31
|
import { CLIError } from '../lib/cli-error.js';
|
|
22
32
|
import { collectClaudePaths } from '../lib/claude-paths.js';
|
|
23
33
|
import { CLAUDE_MD_FILE } from '../lib/constants.js';
|
|
34
|
+
import { MIGRATIONS_DIR, SCHEMA_DIR, needsInitialMigration, readMigrationFacts, } from '../lib/initial-migration.js';
|
|
24
35
|
import { hasLockfile, normalizeThenHash, readLockfile } from '../lib/lockfile.js';
|
|
25
36
|
import { runLauncherPreflight, } from '../lib/preflight.js';
|
|
26
37
|
import { detectRepo } from '../lib/repo-detection.js';
|
|
@@ -46,6 +57,18 @@ export const A1_WARNING = 'Aviso de seguridad (A1): el boilerplate `src/` y sus
|
|
|
46
57
|
'dependencia) NO llega por este canal. El canal de parches de `src/` es EPIC 2 ' +
|
|
47
58
|
'(update agentico con merge inteligente), aún no disponible. Mantén `src/` y tus ' +
|
|
48
59
|
'dependencias al día por tu cuenta mientras tanto.';
|
|
60
|
+
/**
|
|
61
|
+
* The literal notice for a derivative whose schema has no generated migration.
|
|
62
|
+
* Named constant (never an inline string — CODING.md §5) and built from the SAME
|
|
63
|
+
* directory constants the provision step uses, so a path can never drift between
|
|
64
|
+
* what `provision` writes and what `doctor` reports. Exported for the tests.
|
|
65
|
+
*/
|
|
66
|
+
export const MISSING_INITIAL_MIGRATION_NOTICE = `⚠ Falta la migración inicial: hay schema en \`${SCHEMA_DIR}/\` y ningún \`.sql\` en ` +
|
|
67
|
+
`\`${MIGRATIONS_DIR}/\`.\n` +
|
|
68
|
+
' El deploy corre `pnpm db:migrate && pnpm build`, así que el primer build migraría una ' +
|
|
69
|
+
'base vacía. Genera la migración con `pnpm db:generate` y commitéala antes del próximo ' +
|
|
70
|
+
'deploy.\n' +
|
|
71
|
+
' (Aviso informativo: `factory doctor` no bloquea — sale con éxito de todos modos.)';
|
|
49
72
|
/** Parse `doctor`'s argv slice into flags. Unknown flags are ignored. */
|
|
50
73
|
export function parseDoctorFlags(argv) {
|
|
51
74
|
return {
|
|
@@ -114,6 +137,10 @@ export function computeDoctor(deps = {}) {
|
|
|
114
137
|
// `doctor` from a subdir of a managed repo reports the real state instead of
|
|
115
138
|
// "no lockfile" (consistency with `add`/`update`). Tests inject `rootDir`.
|
|
116
139
|
const rootDir = deps.rootDir ?? detectRepo(process.cwd()).repoRoot ?? process.cwd();
|
|
140
|
+
// Initial-migration gap (GAP-002). Delegated WHOLE to the provision predicate —
|
|
141
|
+
// facts read from disk, decision (Factory exception included) taken there. Doctor
|
|
142
|
+
// does not re-derive "is this the Factory": one copy of that rule, in one place.
|
|
143
|
+
const missingInitialMigration = needsInitialMigration(readMigrationFacts(rootDir));
|
|
117
144
|
if (!hasLockfile(rootDir)) {
|
|
118
145
|
return {
|
|
119
146
|
orphans: [],
|
|
@@ -121,6 +148,9 @@ export function computeDoctor(deps = {}) {
|
|
|
121
148
|
unimportedRules: [],
|
|
122
149
|
claudeMdMissing: false,
|
|
123
150
|
a1Warning: A1_WARNING,
|
|
151
|
+
// Always false on this branch, and by the predicate rather than by a literal:
|
|
152
|
+
// no lockfile IS the Factory signal, which `needsInitialMigration` short-circuits.
|
|
153
|
+
missingInitialMigration,
|
|
124
154
|
noLockfile: true,
|
|
125
155
|
beta: false,
|
|
126
156
|
};
|
|
@@ -174,6 +204,7 @@ export function computeDoctor(deps = {}) {
|
|
|
174
204
|
unimportedRules,
|
|
175
205
|
claudeMdMissing,
|
|
176
206
|
a1Warning: A1_WARNING,
|
|
207
|
+
missingInitialMigration,
|
|
177
208
|
noLockfile: false,
|
|
178
209
|
beta,
|
|
179
210
|
};
|
|
@@ -181,6 +212,8 @@ export function computeDoctor(deps = {}) {
|
|
|
181
212
|
/** Render the doctor result to the console. The A1 notice is ALWAYS last. */
|
|
182
213
|
function renderDoctor(result) {
|
|
183
214
|
if (result.noLockfile) {
|
|
215
|
+
// Nothing to say about migrations here: no lockfile IS the Factory signal, so
|
|
216
|
+
// `missingInitialMigration` is false by construction on this branch.
|
|
184
217
|
console.log('Sin lockfile detectado.');
|
|
185
218
|
console.log('Ejecuta `factory:update` para inicializar el tracking de versión.');
|
|
186
219
|
// The A1 notice is emitted even without a lockfile (always present).
|
|
@@ -223,6 +256,12 @@ function renderDoctor(result) {
|
|
|
223
256
|
}
|
|
224
257
|
}
|
|
225
258
|
}
|
|
259
|
+
// Initial-migration gap (GAP-002): reported, never enforced. It sits outside the
|
|
260
|
+
// orphans/conflicts block because it is about the derivative's `src/`, not about
|
|
261
|
+
// brain drift — a repo with a spotless brain can still be missing its `0000`.
|
|
262
|
+
if (result.missingInitialMigration) {
|
|
263
|
+
console.log(`\n${MISSING_INITIAL_MIGRATION_NOTICE}`);
|
|
264
|
+
}
|
|
226
265
|
// The A1 security notice is always emitted at the end (design §1 + §11).
|
|
227
266
|
console.log(`\n${result.a1Warning}`);
|
|
228
267
|
}
|