@timekast/factory 1.17.0 → 1.17.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.
@@ -1556,7 +1556,7 @@ async function retireInterimDomain(projectId, token, teamId, state, deps, fetchI
1556
1556
  // The DEV domain is intentionally never touched (issue §4 — conserved).
1557
1557
  }
1558
1558
  /**
1559
- * Update `EMAIL_FROM` + `AUTH_URL`/`NEXTAUTH_URL` to the client domain in the
1559
+ * Update `EMAIL_FROM` + `AUTH_URL`/`NEXTAUTH_URL` + `NEXT_PUBLIC_APP_URL` to the client domain in the
1560
1560
  * Vercel env, per-target and idempotently (PROV-006 §4). Lists the existing vars
1561
1561
  * once; an `AUTH_URL`/`NEXTAUTH_URL`/`EMAIL_FROM` already set to the target value
1562
1562
  * has its PATCH omitted (issue §8 — second run is a no-op). No-echo: only KEY
@@ -1574,6 +1574,9 @@ async function updateDomainEnv(projectId, token, teamId, domain, deps) {
1574
1574
  },
1575
1575
  { key: 'AUTH_URL', value: authUrl, target: ['production', 'preview'] },
1576
1576
  { key: 'NEXTAUTH_URL', value: authUrl, target: ['production', 'preview'] },
1577
+ // Keep the public app origin in lockstep with AUTH_URL once a client domain lands
1578
+ // (the substrate set it to the interim domain in buildEnvPlan; this re-points it).
1579
+ { key: 'NEXT_PUBLIC_APP_URL', value: authUrl, target: ['production', 'preview'] },
1577
1580
  ];
1578
1581
  const existing = await listEnvVars(projectId, token, teamId, deps);
1579
1582
  for (const entry of plan) {
@@ -1599,7 +1602,7 @@ function printClientDomainPlan(domain, interim, route, confirm) {
1599
1602
  console.log(` … sondea GET /v9/projects/.../domains/${domain} hasta verified:true (o timeout)`);
1600
1603
  console.log(` ${confirm ? '-' : '? (requiere --confirm o prompt)'} retira el interino ${interim} ` +
1601
1604
  `(el dev ${domain}-dev se conserva)`);
1602
- console.log(` ↻ EMAIL_FROM=noreply@${mailDomainFor(domain)} · AUTH_URL=https://${domain} · NEXTAUTH_URL=https://${domain}`);
1605
+ console.log(` ↻ EMAIL_FROM=noreply@${mailDomainFor(domain)} · AUTH_URL=https://${domain} · NEXTAUTH_URL=https://${domain} · NEXT_PUBLIC_APP_URL=https://${domain}`);
1603
1606
  console.log(` + Resend: POST /domains { name: ${mailDomainFor(domain)} } (subdominio mail, no el apex)`);
1604
1607
  console.log('\n(`--dry-run`) No se ejecutó ninguna llamada a API ni se escribió estado.');
1605
1608
  }
package/dist/index.js CHANGED
@@ -151,6 +151,13 @@ async function main(argv) {
151
151
  console.log(HELP);
152
152
  return;
153
153
  }
154
+ // A `--help`/`-h` flag ANYWHERE (e.g. `factory env push --help`) prints help and
155
+ // NEVER runs the command. A help flag must never trigger a mutation: `env push --help`
156
+ // used to fire a REAL Vercel push, because the check above only inspects the first arg.
157
+ if (rest.includes('--help') || rest.includes('-h')) {
158
+ console.log(HELP);
159
+ return;
160
+ }
154
161
  // No command at all → interactive TUI menu (LTUI-001). The menu degrades to
155
162
  // HELP itself when there's no TTY (CI / pipe), so it never hangs.
156
163
  if (command === undefined) {
@@ -26,21 +26,21 @@ import { CLIError } from './cli-error.js';
26
26
  import { CLAUDE_MD_FILE, TIMEKAST_DIR } from './constants.js';
27
27
  import { parseLockfile } from './lockfile.js';
28
28
  /** Roots a manifest path may live under (mirrors `profiles.json#track`). */
29
- const ALLOWED_ROOTS = ['.claude/', 'scripts/'];
29
+ const ALLOWED_ROOTS = ['.claude/', 'scripts/', '.husky/'];
30
30
  /**
31
31
  * Reject a manifest path that is unsafe to write: absolute, escaping via `..`, or
32
- * outside the tracked scope (`.claude/**`, `scripts/**`, `CLAUDE.md`). A correct
32
+ * outside the tracked scope (`.claude/**`, `scripts/**`, `CLAUDE.md`, `.husky/**`). A correct
33
33
  * release never produces these — this guards a mispublished/tampered tarball from
34
34
  * writing `src/` or escaping the repo root. Separator-agnostic (Windows-safe).
35
35
  */
36
- function assertSafeTrackedPath(rel) {
36
+ export function assertSafeTrackedPath(rel) {
37
37
  const segments = rel.split(/[\\/]/);
38
38
  if (rel.length === 0 || path.isAbsolute(rel) || segments.includes('..')) {
39
39
  throw new CLIError(`El manifest declara un path inseguro \`${rel}\` (absoluto o con \`..\`); no se tocó nada.`);
40
40
  }
41
41
  const tracked = rel === CLAUDE_MD_FILE || ALLOWED_ROOTS.some((root) => rel.startsWith(root));
42
42
  if (!tracked) {
43
- throw new CLIError(`El manifest declara \`${rel}\`, fuera del scope rastreado (\`.claude/\`, \`scripts/\`, \`CLAUDE.md\`). ` +
43
+ throw new CLIError(`El manifest declara \`${rel}\`, fuera del scope rastreado (\`.claude/\`, \`scripts/\`, \`CLAUDE.md\`, \`.husky/\`). ` +
44
44
  'El release está mal construido; no se tocó nada.');
45
45
  }
46
46
  }
@@ -25,6 +25,10 @@ export const PROVISION_OWNED = new Set([
25
25
  'DATABASE_URL_MAIN',
26
26
  'AUTH_SECRET',
27
27
  'NEXT_PUBLIC_APP_ENV',
28
+ // Per-env app origin: provision sets it to the real domain per environment
29
+ // (prod/preview); `env:push` must NOT re-upload the `localhost:3000` default of
30
+ // `.env.local`, which would break links / OAuth callbacks / the PWA manifest in prod.
31
+ 'NEXT_PUBLIC_APP_URL',
28
32
  // Provision mints these per-env too (P-1): the MFA key (base64url) + the
29
33
  // WebAuthn rpID (the exact host). A shared day-2 push would break passkeys
30
34
  // (rpID must match the env's host) or the at-rest TOTP crypto.
@@ -470,6 +470,20 @@ export function buildEnvPlan(input) {
470
470
  // Deployment identity — drives the env-aware PWA manifest (see .env.example).
471
471
  { key: 'NEXT_PUBLIC_APP_ENV', value: 'production', target: ['production'] },
472
472
  { key: 'NEXT_PUBLIC_APP_ENV', value: 'preview', target: ['preview'] },
473
+ // App origin — the EXACT per-env URL (same host as WEBAUTHN_RP_ID), so links,
474
+ // OAuth callbacks and the PWA manifest resolve to the real domain instead of the
475
+ // `localhost:3000` default of `.env.local`. Per-env (PROVISION_OWNED) → `env:push`
476
+ // never overwrites it with a single shared value (P-1 parity with the other hosts).
477
+ {
478
+ key: 'NEXT_PUBLIC_APP_URL',
479
+ value: `https://${input.webauthnRpIdProd}`,
480
+ target: ['production'],
481
+ },
482
+ {
483
+ key: 'NEXT_PUBLIC_APP_URL',
484
+ value: `https://${input.webauthnRpIdPreview}`,
485
+ target: ['preview'],
486
+ },
473
487
  ];
474
488
  for (const [key, value] of Object.entries(input.shared ?? {})) {
475
489
  if (!value)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekast/factory",
3
- "version": "1.17.0",
3
+ "version": "1.17.2",
4
4
  "description": "Public, thin CLI to bootstrap and maintain TimeKast Factory derived projects.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",