@timekast/factory 1.5.0 → 1.6.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.
@@ -18,7 +18,7 @@ import prompts from 'prompts';
18
18
  import { CLIError } from '../lib/cli-error.js';
19
19
  import { FACTORY_ORG, PROFILES } from '../lib/constants.js';
20
20
  import { parseLockfile, writeInitialLockfile } from '../lib/lockfile.js';
21
- import { applyPackageJsonEdits } from '../lib/package-json.js';
21
+ import { applyPackageJsonEdits, kitLocalScripts } from '../lib/package-json.js';
22
22
  import { renderDerivedReadme } from '../lib/readme.js';
23
23
  import { runPreflight } from '../lib/preflight.js';
24
24
  import { downloadProfileTarball, moveContentsInto, stageProfileTarball } from '../lib/unpack.js';
@@ -103,7 +103,7 @@ export async function runNew(name) {
103
103
  const pkgPath = path.join(destDir, 'package.json');
104
104
  const hasPackageJson = existsSync(pkgPath);
105
105
  if (hasPackageJson) {
106
- const { content, scriptAlreadyPresent } = applyPackageJsonEdits(readFileSync(pkgPath, 'utf8'), validName);
106
+ const { content, scriptAlreadyPresent } = applyPackageJsonEdits(readFileSync(pkgPath, 'utf8'), validName, kitLocalScripts(destDir));
107
107
  writeFileSync(pkgPath, content, 'utf8');
108
108
  if (scriptAlreadyPresent) {
109
109
  console.warn('Aviso: `factory:update` ya existía en package.json con otro valor; no se sobrescribió.');
@@ -83,3 +83,14 @@ export const FACTORY_SCRIPTS = {
83
83
  [PUBLISH_SCRIPT_NAME]: PUBLISH_SCRIPT_CMD,
84
84
  [UNPUBLISH_SCRIPT_NAME]: UNPUBLISH_SCRIPT_CMD,
85
85
  };
86
+ /**
87
+ * Kit-local convenience script: the readiness sweep (`pnpm preflight`, consumed
88
+ * by the /deploy gate and /preflight). Unlike the npx-based `factory:*` scripts
89
+ * it shells a file that ships with the FULL brain, so the installer ensures it
90
+ * only when that file exists in the repo (file-gated — a core/non-Factory repo
91
+ * never gets a broken entry). Insert-if-missing like the rest: a dev's own
92
+ * `preflight` script is never overwritten.
93
+ */
94
+ export const PREFLIGHT_SCRIPT_NAME = 'preflight';
95
+ export const PREFLIGHT_SCRIPT_CMD = 'tsx scripts/tools/preflight.ts';
96
+ export const PREFLIGHT_SCRIPT_FILE = 'scripts/tools/preflight.ts';
@@ -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, PROFILES, UPDATE_SCRIPT_NAME } from './constants.js';
11
+ import { FACTORY_SCRIPTS, 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
@@ -66,12 +66,12 @@ export function parsePackageJson(raw) {
66
66
  * only on a real edit) + the result for the primary `factory:update` script
67
67
  * (which drives the install messaging).
68
68
  */
69
- function ensureFactoryScripts(pkg) {
69
+ function ensureFactoryScripts(pkg, extraScripts = {}) {
70
70
  const scripts = pkg.scripts ?? {};
71
71
  pkg.scripts = scripts;
72
72
  let changed = false;
73
73
  let primary = { action: 'already-correct' };
74
- for (const [name, cmd] of Object.entries(FACTORY_SCRIPTS)) {
74
+ for (const [name, cmd] of Object.entries({ ...FACTORY_SCRIPTS, ...extraScripts })) {
75
75
  const existing = scripts[name];
76
76
  let result;
77
77
  if (existing === undefined) {
@@ -91,27 +91,41 @@ function ensureFactoryScripts(pkg) {
91
91
  return { changed, primary };
92
92
  }
93
93
  /**
94
- * Rename `package.json.name` and ensure the `factory:*` scripts exist,
95
- * preserving everything else. Returns the serialized document (with the
96
- * original indentation + trailing newline).
94
+ * The kit-local scripts to ensure for THIS repo: `preflight` only when its
95
+ * backing file ships here (full brain). File-gated so a core/non-Factory repo
96
+ * never gets an entry that shells a file it does not have — and it self-heals:
97
+ * the entry appears on the first install/update after the file lands.
98
+ */
99
+ export function kitLocalScripts(rootDir) {
100
+ return existsSync(path.join(rootDir, PREFLIGHT_SCRIPT_FILE))
101
+ ? { [PREFLIGHT_SCRIPT_NAME]: PREFLIGHT_SCRIPT_CMD }
102
+ : {};
103
+ }
104
+ /**
105
+ * Rename `package.json.name` and ensure the `factory:*` scripts (+ the
106
+ * file-gated kit-local `extraScripts`, see `kitLocalScripts`) exist, preserving
107
+ * everything else. Returns the serialized document (with the original
108
+ * indentation + trailing newline).
97
109
  *
98
110
  * If `factory:update` already exists with a different value it is left intact;
99
111
  * `scriptAlreadyPresent` reports that so the caller can warn the user.
100
112
  */
101
- export function applyPackageJsonEdits(raw, name) {
113
+ export function applyPackageJsonEdits(raw, name, extraScripts = {}) {
102
114
  const indent = detectIndent(raw);
103
115
  const pkg = parsePackageJson(raw);
104
116
  pkg.name = name;
105
- const { primary } = ensureFactoryScripts(pkg);
117
+ const { primary } = ensureFactoryScripts(pkg, extraScripts);
106
118
  const content = `${JSON.stringify(pkg, null, indent)}\n`;
107
119
  return { content, scriptAlreadyPresent: primary.action === 'conflict' };
108
120
  }
109
121
  /**
110
122
  * Surgically ensure the `factory:*` scripts (`update` / `doctor` / `status`)
111
- * exist in the `package.json` at `pkgPath` (design §7.4). Reads, mutates only
112
- * those keys, and re-serializes with the original indentation + trailing
113
- * newline. NEVER overwrites a divergent value and NEVER touches `name`, deps,
114
- * or any other script.
123
+ * plus the file-gated kit-local scripts (`preflight` when its file ships
124
+ * `kitLocalScripts` on the package.json's own directory) exist in the
125
+ * `package.json` at `pkgPath` (design §7.4). Reads, mutates only those keys,
126
+ * and re-serializes with the original indentation + trailing newline. NEVER
127
+ * overwrites a divergent value and NEVER touches `name`, deps, or any other
128
+ * script.
115
129
  *
116
130
  * Writes the file only when at least one script was added; if all are already
117
131
  * present (correct or divergent) the file is left byte-identical.
@@ -123,7 +137,7 @@ export function insertFactoryUpdateScript(pkgPath) {
123
137
  const raw = readFileSync(pkgPath, 'utf8');
124
138
  const indent = detectIndent(raw);
125
139
  const pkg = parsePackageJson(raw);
126
- const { changed, primary } = ensureFactoryScripts(pkg);
140
+ const { changed, primary } = ensureFactoryScripts(pkg, kitLocalScripts(path.dirname(pkgPath)));
127
141
  if (changed) {
128
142
  writeFileSync(pkgPath, `${JSON.stringify(pkg, null, indent)}\n`, 'utf8');
129
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekast/factory",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Public, thin CLI to bootstrap and maintain TimeKast Factory derived projects.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",