@timekast/factory 1.28.0 → 1.28.1
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/provision.js +26 -11
- package/dist/lib/neon-api.js +25 -25
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@ import prompts from 'prompts';
|
|
|
26
26
|
import { FACTORY_ORG } from '../lib/constants.js';
|
|
27
27
|
import { CLIError } from '../lib/cli-error.js';
|
|
28
28
|
import { apexZoneOf, CloudflareApiError, CLOUDFLARE_API_BASE, deleteDnsRecord, ensureCnameRecord, ensureDnsRecord, findCnameRecord, resolveZoneId, resolveZoneIdForName, resolveZoneIdForDomain, CNAME_VERCEL_TARGET, TIMEKAST_ZONE_NAME, provisionDomains, existingProvisionDomains, ORG_ZONE_NAMES, } from '../lib/cloudflare-api.js';
|
|
29
|
-
import { createDevelopBranch, createNeonProject, createProjectApiKey, deleteNeonApiKeysByName, NEON_MAIN_BRANCH,
|
|
29
|
+
import { createDevelopBranch, createNeonProject, createProjectApiKey, deleteNeonApiKeysByName, NEON_MAIN_BRANCH, getNeonProjectOrg, listNeonOrgs, resolveNeonOrg, deleteNeonProject, getConnectionUri, listNeonBranches, listNeonProjects, NeonApiError, NEON_API_BASE, NEON_DEVELOP_BRANCH, } from '../lib/neon-api.js';
|
|
30
30
|
import { createBacklogProject, deleteBacklogProject, mintBacklogKey, BacklogApiError, } from '../lib/backlog-api.js';
|
|
31
31
|
import { defaultEnvPath, readEnvKeyNames, readVercelToken, runProvisionPreflight, } from '../lib/provision-preflight.js';
|
|
32
32
|
import { addVercelDomain, buildEnvPlan, createVercelProject, generateAuthSecret, generateMfaEncryptionKey, getLinkedRepoId, getVercelDomainStatus, getVercelProjectByName, GitHubAppNotInstalledError, linkGitRepo, listProjectDomains, listVercelProjects, patchProjectSettings, pushEnvPlan, removeVercelDomain, removeVercelProject, triggerDeployment, upsertEnvVar, listEnvVars, VercelApiError, VERCEL_AUTO_APEX, writeVercelProjectJson, } from '../lib/vercel-env.js';
|
|
@@ -565,7 +565,7 @@ export async function provisionNeon(rootDir, state, deps = {}) {
|
|
|
565
565
|
// already exists — that distinction is the whole point:
|
|
566
566
|
// - already recorded in the state → use it verbatim, no call, no guessing;
|
|
567
567
|
// - exists in Neon but the state predates `orgId` (or this is a --remint on an
|
|
568
|
-
// older stack) →
|
|
568
|
+
// older stack) → ask Neon which org owns that project ID (`getNeonProjectOrg`);
|
|
569
569
|
// - brand new → resolve the selector / the rail default / the only org.
|
|
570
570
|
// Pointing a teardown or a re-mint at the default instead of the project's real
|
|
571
571
|
// org is exactly the class of error this whole change is fixing.
|
|
@@ -573,8 +573,19 @@ export async function provisionNeon(rootDir, state, deps = {}) {
|
|
|
573
573
|
if (!orgId) {
|
|
574
574
|
try {
|
|
575
575
|
if (state.neon?.projectId) {
|
|
576
|
-
|
|
577
|
-
|
|
576
|
+
// 🔴 BY ID, not by name. Asking which project is called like this repo's folder
|
|
577
|
+
// fails for almost every real project — they are named by hand in the console
|
|
578
|
+
// (`Aditivo CRM`, `agent-fimubac`) — and the fallback then aims at the default
|
|
579
|
+
// org: silently right inside it, `404 project not found` outside. Both happened
|
|
580
|
+
// the same afternoon. With the id recorded, one GET answers it exactly.
|
|
581
|
+
orgId = (await getNeonProjectOrg(apiKey, state.neon.projectId, apiDeps)) ?? undefined;
|
|
582
|
+
if (!orgId) {
|
|
583
|
+
throw new CLIError(`El proyecto Neon \`${state.neon.projectId}\` que registra el estado de este ` +
|
|
584
|
+
'repo no existe o la key del rail no lo alcanza.\n' +
|
|
585
|
+
' Si el proyecto se borró, reconstruye el estado con `factory provision --adopt ' +
|
|
586
|
+
'--force`. Si existe, revisa que la `NEON_API_KEY` del rail sea la de la cuenta ' +
|
|
587
|
+
'de servicio con acceso a su organización.');
|
|
588
|
+
}
|
|
578
589
|
}
|
|
579
590
|
if (!orgId) {
|
|
580
591
|
const selector = deps.neonOrg ?? readGateValue('NEON_ORG', envPath) ?? undefined;
|
|
@@ -582,11 +593,13 @@ export async function provisionNeon(rootDir, state, deps = {}) {
|
|
|
582
593
|
}
|
|
583
594
|
}
|
|
584
595
|
catch (error) {
|
|
596
|
+
if (error instanceof CLIError)
|
|
597
|
+
throw error;
|
|
585
598
|
if (error instanceof NeonApiError) {
|
|
586
599
|
throw neonErrorToCliError(error, state.projectName, 'resolver la organización de Neon');
|
|
587
600
|
}
|
|
588
|
-
// resolveNeonOrg
|
|
589
|
-
//
|
|
601
|
+
// resolveNeonOrg throws plain Errors whose message is already the es-MX text the
|
|
602
|
+
// user needs; surface it as-is.
|
|
590
603
|
throw new CLIError(error instanceof Error ? error.message : String(error));
|
|
591
604
|
}
|
|
592
605
|
}
|
|
@@ -3301,9 +3314,9 @@ async function destroyNeonBranch(target, apiKey, fetchImpl) {
|
|
|
3301
3314
|
*
|
|
3302
3315
|
* The org id is resolved BEFORE the nuke, because after it the project is gone and
|
|
3303
3316
|
* nothing can say which org owned it any more. It comes from the state when present
|
|
3304
|
-
* and is
|
|
3305
|
-
* existed takes that path). Unresolvable → the project is still nuked and only the
|
|
3306
|
-
*
|
|
3317
|
+
* and is asked of Neon BY PROJECT ID otherwise (every stack provisioned before `orgId`
|
|
3318
|
+
* existed takes that path). Unresolvable → the project is still nuked and only the key
|
|
3319
|
+
* cleanup is skipped, with the key named so it can be revoked by hand.
|
|
3307
3320
|
*/
|
|
3308
3321
|
async function destroyNeonProject(target, apiKey, fetchImpl) {
|
|
3309
3322
|
const resource = `Neon project (${target.neonProjectId})`;
|
|
@@ -3313,9 +3326,11 @@ async function destroyNeonProject(target, apiKey, fetchImpl) {
|
|
|
3313
3326
|
const keyName = `${target.slug}-e2e`;
|
|
3314
3327
|
let orgId = target.neonOrgId;
|
|
3315
3328
|
if (!orgId) {
|
|
3329
|
+
// BY ID (see `getNeonProjectOrg`): the project's name in the console rarely equals
|
|
3330
|
+
// the repo's slug, so the by-name sweep this used to do found nothing for most real
|
|
3331
|
+
// projects — and the key cleanup was then skipped for no good reason.
|
|
3316
3332
|
try {
|
|
3317
|
-
|
|
3318
|
-
orgId = found?.org.id ?? null;
|
|
3333
|
+
orgId = await getNeonProjectOrg(apiKey, target.neonProjectId, { fetchImpl });
|
|
3319
3334
|
}
|
|
3320
3335
|
catch {
|
|
3321
3336
|
orgId = null;
|
package/dist/lib/neon-api.js
CHANGED
|
@@ -171,36 +171,36 @@ export async function resolveNeonOrg(apiKey, selector, deps = {}) {
|
|
|
171
171
|
'`NEON_ORG=<nombre>` en `~/.claude/.env`.');
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
|
-
*
|
|
175
|
-
* return it together with the org that owns it.
|
|
174
|
+
* The organization that owns a project, asked BY ID — `GET /projects/{id}`.
|
|
176
175
|
*
|
|
177
|
-
* This is
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
176
|
+
* 🔴 This is the right question whenever the project id is known, and the reason is a
|
|
177
|
+
* bug that shipped: the org used to be resolved by sweeping every org for a project
|
|
178
|
+
* whose NAME equals the repo's slug. Projects are named by humans in the console
|
|
179
|
+
* (`Aditivo CRM`, `agent-fimubac`), so that match usually fails — and when it fails the
|
|
180
|
+
* caller falls back to a configured default. In the org that happens to be the default
|
|
181
|
+
* it silently "works"; outside it, the mint 404s with `project not found`. Observed on
|
|
182
|
+
* both, the same afternoon. With an id in hand, asking by name is a detour.
|
|
181
183
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
+
* Returns `null` when the project does not exist or the key cannot see it — the caller
|
|
185
|
+
* then decides (a re-mint refuses; nothing is ever created on a guess).
|
|
184
186
|
*
|
|
185
|
-
* @
|
|
187
|
+
* @throws {NeonApiError} on a non-2xx that is NOT 404 (auth, quota, network): those are
|
|
188
|
+
* real failures and must not be read as "no such project".
|
|
186
189
|
*/
|
|
187
|
-
export async function
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (hits.length > 1) {
|
|
199
|
-
throw new Error(`Hay un proyecto de Neon llamado \`${projectName}\` en más de una organización: ` +
|
|
200
|
-
`${hits.map((h) => `${h.org.name} (${h.project.id})`).join(', ')}.\n` +
|
|
201
|
-
' No elijo por ti: resuélvelo a mano en la consola de Neon (renombra uno) y reintenta.');
|
|
190
|
+
export async function getNeonProjectOrg(apiKey, projectId, deps = {}) {
|
|
191
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
192
|
+
const response = await fetchImpl(`${NEON_API_BASE}/projects/${encodeURIComponent(projectId)}`, {
|
|
193
|
+
method: 'GET',
|
|
194
|
+
headers: neonHeaders(apiKey),
|
|
195
|
+
signal: AbortSignal.timeout(NEON_API_TIMEOUT_MS),
|
|
196
|
+
});
|
|
197
|
+
if (response.status === 404)
|
|
198
|
+
return null;
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
throw new NeonApiError(response.status, await safeBody(response), 'GET /projects/{id}');
|
|
202
201
|
}
|
|
203
|
-
|
|
202
|
+
const data = (await response.json());
|
|
203
|
+
return data.project?.org_id ?? null;
|
|
204
204
|
}
|
|
205
205
|
/**
|
|
206
206
|
* Create a Neon project. The project is born WITH its primary branch — we name
|