@timekast/factory 1.17.3 → 1.18.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/provision.js +13 -1
- package/dist/lib/constants.js +35 -18
- package/dist/lib/env-file.js +29 -0
- package/dist/lib/package-json.js +9 -1
- package/package.json +2 -1
|
@@ -29,7 +29,7 @@ import { apexZoneOf, CloudflareApiError, CLOUDFLARE_API_BASE, ensureCnameRecord,
|
|
|
29
29
|
import { createDevelopBranch, createNeonProject, createProjectApiKey, deleteNeonApiKeysByName, deleteNeonProject, getConnectionUri, listNeonBranches, listNeonProjects, NeonApiError, NEON_API_BASE, NEON_DEVELOP_BRANCH, } from '../lib/neon-api.js';
|
|
30
30
|
import { defaultEnvPath, readVercelToken, runProvisionPreflight, } from '../lib/provision-preflight.js';
|
|
31
31
|
import { addVercelDomain, buildEnvPlan, createVercelProject, generateAuthSecret, generateMfaEncryptionKey, getLinkedRepoId, getVercelDomainStatus, GitHubAppNotInstalledError, linkGitRepo, listVercelProjects, patchProjectSettings, pushEnvPlan, removeVercelDomain, triggerDeployment, upsertEnvVar, listEnvVars, VercelApiError, writeVercelProjectJson, } from '../lib/vercel-env.js';
|
|
32
|
-
import { writeE2eLocalCreds, writeEnvLocal } from '../lib/env-file.js';
|
|
32
|
+
import { writeE2eLocalCreds, writeEmailLocalCreds, writeEnvLocal } from '../lib/env-file.js';
|
|
33
33
|
import { adoptState, ADOPT_UNKNOWN, clearProvisionState, clearState, hasProvisionState, isServiceComplete, PROVISION_DIR, PROVISION_STATE_FILE, readDestroyTarget, readProvisionState, writeProvisionState, } from '../lib/provision-state.js';
|
|
34
34
|
/**
|
|
35
35
|
* The THREE GitHub Secrets the E2E runner (`pnpm test:e2e` → `e2e-runner.ts` +
|
|
@@ -752,6 +752,12 @@ export async function provisionVercel(rootDir, state, teamId, deps = {}) {
|
|
|
752
752
|
writeE2eLocalCreds(rootDir, state.neon.projectId, readNeonApiKey(envPath))) {
|
|
753
753
|
console.log(' + .env.local (NEON_API_KEY · NEON_PROJECT_ID — para `pnpm test:e2e` local)');
|
|
754
754
|
}
|
|
755
|
+
// Email config: write the org Resend key (from the rail) + the fixed EMAIL_FROM so local
|
|
756
|
+
// sends (`invite:admin`, magic-link, `db:seed`) DELIVER instead of the console fallback.
|
|
757
|
+
// No-op without a rail key. Non-prod sends are auto-tagged `[Dev]` by the dispatcher.
|
|
758
|
+
if (writeEmailLocalCreds(rootDir, readGateValue('RESEND_API_KEY', envPath), PROVISION_EMAIL_FROM)) {
|
|
759
|
+
console.log(' + .env.local (EMAIL_PROVIDER=resend · RESEND_API_KEY · EMAIL_FROM — envío de correo local)');
|
|
760
|
+
}
|
|
755
761
|
// `pnpm install` the derived dir (issue §4). A missing package.json (non-Node
|
|
756
762
|
// derivative) is reported, NOT fatal (issue §8).
|
|
757
763
|
if (existsSync(join(rootDir, 'package.json'))) {
|
|
@@ -2499,6 +2505,12 @@ export async function runProvision(flags) {
|
|
|
2499
2505
|
console.log(' + .env.local (NEON_API_KEY · NEON_PROJECT_ID — para `pnpm test:e2e` local)');
|
|
2500
2506
|
}
|
|
2501
2507
|
}
|
|
2508
|
+
// Same for email config on resume/adopt (writeEmailLocalCreds only runs in the first
|
|
2509
|
+
// provisionVercel pass). Idempotent — setEnvLine replaces/appends. No-op without a rail key.
|
|
2510
|
+
if (!flags.dryRun &&
|
|
2511
|
+
writeEmailLocalCreds(rootDir, readGateValue('RESEND_API_KEY', defaultEnvPath()), PROVISION_EMAIL_FROM)) {
|
|
2512
|
+
console.log(' + .env.local (EMAIL_PROVIDER=resend · RESEND_API_KEY · EMAIL_FROM — envío de correo local)');
|
|
2513
|
+
}
|
|
2502
2514
|
// 5b. Client domain (PROV-006) — runs AFTER the services so the Vercel project
|
|
2503
2515
|
// (and the interim domains) already exist. Standalone patch or post-services.
|
|
2504
2516
|
if (hasDomain) {
|
package/dist/lib/constants.js
CHANGED
|
@@ -123,38 +123,39 @@ export const INVITE_ADMIN_SCRIPT_CMD = 'npx @timekast/factory invite-admin';
|
|
|
123
123
|
export const INVITE_ADMIN_SCRIPT_FILE = 'scripts/tools/invite-admin.ts';
|
|
124
124
|
/**
|
|
125
125
|
* Kit-local reference generators — autogen of `project/reference/*` (INVENTORY,
|
|
126
|
-
* CODEBASE, HOOKS, SCHEMA, API). Each shells a `scripts/tools/generate-*.
|
|
127
|
-
* that ships with the FULL brain, so
|
|
128
|
-
* its backing file exists (file-gated,
|
|
129
|
-
* `preflight`). A derivative bootstrapped
|
|
130
|
-
* the alias on the next `update`/`beta`
|
|
131
|
-
* in the tracked set, then the alias
|
|
126
|
+
* CODEBASE, HOOKS, SCHEMA, API). Each shells a `scripts/tools/generate-*.ts`
|
|
127
|
+
* (run via `tsx`, same as the other TS tools) that ships with the FULL brain, so
|
|
128
|
+
* the installer ensures each alias ONLY when its backing file exists (file-gated,
|
|
129
|
+
* insert-if-missing — same contract as `preflight`). A derivative bootstrapped
|
|
130
|
+
* before a generator existed self-heals the alias on the next `update`/`beta`
|
|
131
|
+
* (the script file arrives via `scripts/**` in the tracked set, then the alias
|
|
132
|
+
* appears). Never overwrites a dev's own value.
|
|
132
133
|
*/
|
|
133
134
|
export const GENERATOR_SCRIPTS = [
|
|
134
135
|
{
|
|
135
136
|
name: 'generate:inventory',
|
|
136
|
-
cmd: '
|
|
137
|
-
file: 'scripts/tools/generate-inventory.
|
|
137
|
+
cmd: 'tsx scripts/tools/generate-inventory.ts',
|
|
138
|
+
file: 'scripts/tools/generate-inventory.ts',
|
|
138
139
|
},
|
|
139
140
|
{
|
|
140
141
|
name: 'generate:codebase',
|
|
141
|
-
cmd: '
|
|
142
|
-
file: 'scripts/tools/generate-codebase.
|
|
142
|
+
cmd: 'tsx scripts/tools/generate-codebase.ts',
|
|
143
|
+
file: 'scripts/tools/generate-codebase.ts',
|
|
143
144
|
},
|
|
144
145
|
{
|
|
145
146
|
name: 'generate:hooks',
|
|
146
|
-
cmd: '
|
|
147
|
-
file: 'scripts/tools/generate-hooks.
|
|
147
|
+
cmd: 'tsx scripts/tools/generate-hooks.ts',
|
|
148
|
+
file: 'scripts/tools/generate-hooks.ts',
|
|
148
149
|
},
|
|
149
150
|
{
|
|
150
151
|
name: 'generate:schema',
|
|
151
|
-
cmd: '
|
|
152
|
-
file: 'scripts/tools/generate-schema.
|
|
152
|
+
cmd: 'tsx scripts/tools/generate-schema.ts',
|
|
153
|
+
file: 'scripts/tools/generate-schema.ts',
|
|
153
154
|
},
|
|
154
155
|
{
|
|
155
156
|
name: 'generate:api',
|
|
156
|
-
cmd: '
|
|
157
|
-
file: 'scripts/tools/generate-api.
|
|
157
|
+
cmd: 'tsx scripts/tools/generate-api.ts',
|
|
158
|
+
file: 'scripts/tools/generate-api.ts',
|
|
158
159
|
},
|
|
159
160
|
];
|
|
160
161
|
/**
|
|
@@ -165,5 +166,21 @@ export const GENERATOR_SCRIPTS = [
|
|
|
165
166
|
* generator set shipped" (they ship together in the FULL brain).
|
|
166
167
|
*/
|
|
167
168
|
export const GENERATE_REFERENCE_NAME = 'generate:reference';
|
|
168
|
-
export const GENERATE_REFERENCE_CMD = '
|
|
169
|
-
export const GENERATE_REFERENCE_GATE_FILE = 'scripts/tools/generate-schema.
|
|
169
|
+
export const GENERATE_REFERENCE_CMD = 'tsx scripts/tools/generate-inventory.ts && tsx scripts/tools/generate-codebase.ts && tsx scripts/tools/generate-hooks.ts && tsx scripts/tools/generate-schema.ts && tsx scripts/tools/generate-api.ts';
|
|
170
|
+
export const GENERATE_REFERENCE_GATE_FILE = 'scripts/tools/generate-schema.ts';
|
|
171
|
+
/**
|
|
172
|
+
* Update-time healing registry: for each kit-owned script, the prior command(s)
|
|
173
|
+
* the kit used to ship. When the kit changes HOW a script is invoked (e.g. the
|
|
174
|
+
* `node`/`.mjs` → `tsx`/`.ts` migration of the reference generators), an existing
|
|
175
|
+
* derivative carries the OLD command in its `package.json`; `factory update`
|
|
176
|
+
* overwrites that stale kit-owned alias with the current command — but NEVER a
|
|
177
|
+
* value the dev diverged on their own (see `ensureFactoryScripts`). Add an entry
|
|
178
|
+
* whenever a kit-owned script's invocation changes, so existing derivatives
|
|
179
|
+
* self-heal on their next update instead of pointing at a deleted file.
|
|
180
|
+
*/
|
|
181
|
+
export const OBSOLETE_SCRIPT_CMDS = {
|
|
182
|
+
...Object.fromEntries(GENERATOR_SCRIPTS.map((g) => [g.name, [`node ${g.file.replace(/\.ts$/, '.mjs')}`]])),
|
|
183
|
+
[GENERATE_REFERENCE_NAME]: [
|
|
184
|
+
GENERATOR_SCRIPTS.map((g) => `node ${g.file.replace(/\.ts$/, '.mjs')}`).join(' && '),
|
|
185
|
+
],
|
|
186
|
+
};
|
package/dist/lib/env-file.js
CHANGED
|
@@ -108,3 +108,32 @@ export function writeE2eLocalCreds(rootDir, projectId, apiKey) {
|
|
|
108
108
|
chmodSync(localPath, 0o600); // holds NEON_API_KEY → owner-only.
|
|
109
109
|
return true;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Write the org email config into `.env.local` so local sends (`invite:admin`, magic-link,
|
|
113
|
+
* `db:seed`) actually DELIVER instead of falling back to the console. Sets `EMAIL_PROVIDER=resend`
|
|
114
|
+
* + `RESEND_API_KEY` (the org key from the rail — already on the dev's host; global, not per-env)
|
|
115
|
+
* + `EMAIL_FROM` (the fixed org sender). Non-production sends are auto-tagged `[Dev]` by the
|
|
116
|
+
* dispatcher (`src/lib/email/index.ts`), so local delivery is safe AND distinguishable.
|
|
117
|
+
*
|
|
118
|
+
* This closes the source-inconsistency bug: `invite-admin --target main` writes to the prod DB
|
|
119
|
+
* but the script always reads email config from `.env.local` — which provision otherwise never
|
|
120
|
+
* populated (it only synced Resend to Vercel), so the invite email silently fell back to console.
|
|
121
|
+
*
|
|
122
|
+
* Returns false (no-op) when the rail key is missing or `.env.local` does not exist yet
|
|
123
|
+
* (`writeEnvLocal` creates it first). Safe on an existing file: `setEnvLine` only touches these
|
|
124
|
+
* three keys, preserving the rest.
|
|
125
|
+
*/
|
|
126
|
+
export function writeEmailLocalCreds(rootDir, resendKey, emailFrom) {
|
|
127
|
+
if (!resendKey)
|
|
128
|
+
return false;
|
|
129
|
+
const localPath = join(rootDir, '.env.local');
|
|
130
|
+
if (!existsSync(localPath))
|
|
131
|
+
return false;
|
|
132
|
+
let body = readFileSync(localPath, 'utf8');
|
|
133
|
+
body = setEnvLine(body, 'EMAIL_PROVIDER', 'resend');
|
|
134
|
+
body = setEnvLine(body, 'RESEND_API_KEY', resendKey);
|
|
135
|
+
body = setEnvLine(body, 'EMAIL_FROM', emailFrom);
|
|
136
|
+
writeFileSync(localPath, body, 'utf8');
|
|
137
|
+
chmodSync(localPath, 0o600); // holds RESEND_API_KEY → owner-only.
|
|
138
|
+
return true;
|
|
139
|
+
}
|
package/dist/lib/package-json.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { CLIError } from './cli-error.js';
|
|
11
|
-
import { FACTORY_SCRIPTS, GENERATE_REFERENCE_CMD, GENERATE_REFERENCE_GATE_FILE, GENERATE_REFERENCE_NAME, GENERATOR_SCRIPTS, INVITE_ADMIN_SCRIPT_CMD, INVITE_ADMIN_SCRIPT_FILE, INVITE_ADMIN_SCRIPT_NAME, PREFLIGHT_SCRIPT_CMD, PREFLIGHT_SCRIPT_FILE, PREFLIGHT_SCRIPT_NAME, PROFILES, UPDATE_SCRIPT_NAME, } from './constants.js';
|
|
11
|
+
import { FACTORY_SCRIPTS, GENERATE_REFERENCE_CMD, GENERATE_REFERENCE_GATE_FILE, GENERATE_REFERENCE_NAME, GENERATOR_SCRIPTS, INVITE_ADMIN_SCRIPT_CMD, INVITE_ADMIN_SCRIPT_FILE, INVITE_ADMIN_SCRIPT_NAME, OBSOLETE_SCRIPT_CMDS, PREFLIGHT_SCRIPT_CMD, PREFLIGHT_SCRIPT_FILE, PREFLIGHT_SCRIPT_NAME, PROFILES, UPDATE_SCRIPT_NAME, } from './constants.js';
|
|
12
12
|
/**
|
|
13
13
|
* Auto-detect the profile to install into an existing repo from its
|
|
14
14
|
* `package.json`: a `factoryVersion` field means the repo was born from the
|
|
@@ -82,6 +82,14 @@ function ensureFactoryScripts(pkg, extraScripts = {}) {
|
|
|
82
82
|
else if (existing === cmd) {
|
|
83
83
|
result = { action: 'already-correct' };
|
|
84
84
|
}
|
|
85
|
+
else if ((OBSOLETE_SCRIPT_CMDS[name] ?? []).includes(existing)) {
|
|
86
|
+
// The value is a KNOWN-OBSOLETE kit command (e.g. the old `node …mjs` that
|
|
87
|
+
// the .ts migration retired) — heal it to the current command. A genuinely
|
|
88
|
+
// dev-divergent value falls through to `conflict` and is left intact.
|
|
89
|
+
scripts[name] = cmd;
|
|
90
|
+
changed = true;
|
|
91
|
+
result = { action: 'healed' };
|
|
92
|
+
}
|
|
85
93
|
else {
|
|
86
94
|
result = { action: 'conflict', existingValue: existing };
|
|
87
95
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timekast/factory",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Public, thin CLI to bootstrap and maintain TimeKast Factory derived projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"prepublishOnly": "pnpm build",
|
|
36
36
|
"test": "vitest run",
|
|
37
37
|
"test:unit": "vitest run tests/unit",
|
|
38
|
+
"test:provision": "vitest run tests/integration/provision-cycle.test.ts",
|
|
38
39
|
"test:watch": "vitest"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|