@biffo/cli 0.59.3 → 0.60.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.
@@ -90,7 +90,7 @@ SDK is versioned `1.0.0` and biffo-template's
90
90
  builds and publishes it to PyPI (via Trusted Publishing) on a pushed
91
91
  `sdk-v*` tag. `>=1.0,<2.0` matches the `"biffo-plugin-sdk": "^1.0"` that
92
92
  `biffo.plugin.json` declares, and the SDK carries its own independent
93
- semver — it is **not** tied to the template's `core.version`, so a major
93
+ semver — it is **not** tied to the template's core version, so a major
94
94
  bump here means the plugin API broke and nothing else.
95
95
 
96
96
  **Ordering caveat.** The release _pipeline_ exists; the _release_ does not
package/dist/index.js CHANGED
@@ -13,16 +13,150 @@ import chalk2 from "chalk";
13
13
  import { Command } from "commander";
14
14
 
15
15
  // src/lib/core-manifest.ts
16
- import { existsSync, readFileSync, readdirSync } from "fs";
17
- import { dirname, join, relative, sep } from "path";
16
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
17
+ import { dirname as dirname2, join as join2, relative, sep } from "path";
18
+ import { fileURLToPath as fileURLToPath2 } from "url";
19
+ import { z as z2 } from "zod";
20
+
21
+ // src/lib/core-version.ts
22
+ import { execFileSync } from "child_process";
23
+ import { existsSync, readFileSync, writeFileSync } from "fs";
24
+ import { dirname, join, parse as parsePath } from "path";
18
25
  import { fileURLToPath } from "url";
19
26
  import { z } from "zod";
20
- var CORE_MANIFEST_FILE = "core-manifest.json";
27
+ var SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
21
28
  var CoreManifestSchema = z.object({
22
- version: z.literal(1),
23
- note: z.string().optional(),
24
- templateOwned: z.array(z.string()).min(1),
25
- userOwned: z.array(z.string()).default([])
29
+ version: z.string().regex(SEMVER, "must be a semver, e.g. 1.2.3")
30
+ });
31
+ var CORE_VERSION_FILE = "core.version";
32
+ var INSTANCE_CORE_FILE = "biffo.core.json";
33
+ function parseCoreVersion(raw) {
34
+ const match = SEMVER.exec(raw.trim());
35
+ if (!match) {
36
+ throw new Error(`Invalid core version ${JSON.stringify(raw)}: expected semver like 1.2.3`);
37
+ }
38
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
39
+ }
40
+ function compareCoreVersions(a, b) {
41
+ const [aMaj, aMin, aPat] = parseCoreVersion(a);
42
+ const [bMaj, bMin, bPat] = parseCoreVersion(b);
43
+ if (aMaj !== bMaj) return aMaj < bMaj ? -1 : 1;
44
+ if (aMin !== bMin) return aMin < bMin ? -1 : 1;
45
+ if (aPat !== bPat) return aPat < bPat ? -1 : 1;
46
+ return 0;
47
+ }
48
+ function readCoreVersionFile(path) {
49
+ const raw = readFileSync(path, "utf8").trim();
50
+ parseCoreVersion(raw);
51
+ return raw;
52
+ }
53
+ function findCoreVersionUpward(startDir) {
54
+ let dir = startDir;
55
+ for (; ; ) {
56
+ const candidate = join(dir, CORE_VERSION_FILE);
57
+ if (existsSync(candidate)) return candidate;
58
+ const parent = dirname(dir);
59
+ if (parent === dir || dir === parsePath(dir).root) return null;
60
+ dir = parent;
61
+ }
62
+ }
63
+ function getLatestCoreVersion(fromDir) {
64
+ const start = fromDir ?? dirname(fileURLToPath(import.meta.url));
65
+ const fromPackage = versionFromPackageJson(start);
66
+ if (fromPackage) return fromPackage;
67
+ const fromTags = latestCoreVersionFromTags(findRepoRoot(start) ?? start, defaultTagRunner, {
68
+ fetch: false
69
+ });
70
+ if (fromTags) return fromTags;
71
+ const path = findCoreVersionUpward(start);
72
+ if (path) return readCoreVersionFile(path);
73
+ throw new Error(
74
+ `Could not determine the core version above ${start}: no package.json version and no core-v* tag.
75
+ Installed from npm, the package's own version answers this. In a template checkout the tags do \u2014 so a checkout with none (a shallow CI clone, or a source download with no git history) cannot say which core it is. Run \`git fetch --tags\` and retry, or use the published CLI (\`npx @biffo/cli\`), whose version is stamped at publish.`
76
+ );
77
+ }
78
+ function versionFromPackageJson(startDir) {
79
+ let dir = startDir;
80
+ for (; ; ) {
81
+ const candidate = join(dir, "package.json");
82
+ if (existsSync(candidate)) {
83
+ try {
84
+ const raw = JSON.parse(readFileSync(candidate, "utf8"));
85
+ if (typeof raw.version === "string" && SEMVER.test(raw.version) && raw.version !== "0.0.0") {
86
+ return raw.version;
87
+ }
88
+ } catch {
89
+ }
90
+ return null;
91
+ }
92
+ const parent = dirname(dir);
93
+ if (parent === dir || dir === parsePath(dir).root) return null;
94
+ dir = parent;
95
+ }
96
+ }
97
+ function findRepoRoot(startDir) {
98
+ let dir = startDir;
99
+ for (; ; ) {
100
+ if (existsSync(join(dir, ".git"))) return dir;
101
+ const parent = dirname(dir);
102
+ if (parent === dir || dir === parsePath(dir).root) return null;
103
+ dir = parent;
104
+ }
105
+ }
106
+ function readInstanceCoreVersion(cwd) {
107
+ const path = join(cwd, INSTANCE_CORE_FILE);
108
+ if (!existsSync(path)) {
109
+ const inherited = join(cwd, CORE_VERSION_FILE);
110
+ return existsSync(inherited) ? readCoreVersionFile(inherited) : null;
111
+ }
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(readFileSync(path, "utf8"));
115
+ } catch (err) {
116
+ throw new Error(`${INSTANCE_CORE_FILE} is not valid JSON: ${err.message}`);
117
+ }
118
+ const result = CoreManifestSchema.safeParse(parsed);
119
+ if (!result.success) {
120
+ const detail = result.error.issues[0]?.message ?? "unexpected shape";
121
+ throw new Error(`${INSTANCE_CORE_FILE} is invalid: ${detail}`);
122
+ }
123
+ return result.data.version;
124
+ }
125
+ function serializeInstanceCoreVersion(version) {
126
+ parseCoreVersion(version);
127
+ return `${JSON.stringify({ version }, null, 2)}
128
+ `;
129
+ }
130
+ function writeInstanceCoreVersion(cwd, version) {
131
+ writeFileSync(join(cwd, INSTANCE_CORE_FILE), serializeInstanceCoreVersion(version));
132
+ }
133
+ function latestCoreVersionFromTags(repo, git = defaultTagRunner, options = {}) {
134
+ if (options.fetch !== false) {
135
+ try {
136
+ git(["-C", repo, "fetch", "--tags", "--quiet"]);
137
+ } catch {
138
+ }
139
+ }
140
+ let out;
141
+ try {
142
+ out = git(["-C", repo, "tag", "--list", "core-v*"]);
143
+ } catch {
144
+ return null;
145
+ }
146
+ const versions = out.split("\n").map((line) => line.trim()).filter((line) => line.startsWith(CORE_TAG_PREFIX)).map((tag) => tag.slice(CORE_TAG_PREFIX.length)).filter((v) => SEMVER.test(v));
147
+ if (versions.length === 0) return null;
148
+ return versions.sort(compareCoreVersions).at(-1) ?? null;
149
+ }
150
+ var CORE_TAG_PREFIX = "core-v";
151
+ var defaultTagRunner = (args) => execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
152
+
153
+ // src/lib/core-manifest.ts
154
+ var CORE_MANIFEST_FILE = "core-manifest.json";
155
+ var CoreManifestSchema2 = z2.object({
156
+ version: z2.literal(1),
157
+ note: z2.string().optional(),
158
+ templateOwned: z2.array(z2.string()).min(1),
159
+ userOwned: z2.array(z2.string()).default([])
26
160
  });
27
161
  var HARD_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
28
162
  ".git",
@@ -44,37 +178,37 @@ var HARD_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
44
178
  function findTemplateRoot(startDir) {
45
179
  let dir = startDir;
46
180
  for (; ; ) {
47
- if (existsSync(join(dir, CORE_MANIFEST_FILE)) && existsSync(join(dir, "core.version"))) {
181
+ if (existsSync2(join2(dir, CORE_MANIFEST_FILE)) && !existsSync2(join2(dir, INSTANCE_CORE_FILE))) {
48
182
  return dir;
49
183
  }
50
- const parent = dirname(dir);
184
+ const parent = dirname2(dir);
51
185
  if (parent === dir) return null;
52
186
  dir = parent;
53
187
  }
54
188
  }
55
189
  function resolveTemplateRoot(options = {}) {
56
- const start = options.fromDir ?? dirname(fileURLToPath(import.meta.url));
190
+ const start = options.fromDir ?? dirname2(fileURLToPath2(import.meta.url));
57
191
  const root = findTemplateRoot(start);
58
192
  if (!root) {
59
193
  const guidance = options.guidance ?? "Point this command at a biffo-template checkout at the target version.";
60
194
  throw new Error(
61
- `Could not locate a Biffo template root (a directory with ${CORE_MANIFEST_FILE} and core.version) above ${start}. ` + guidance
195
+ `Could not locate a Biffo template root (a directory with ${CORE_MANIFEST_FILE} and no ${INSTANCE_CORE_FILE}) above ${start}. ` + guidance
62
196
  );
63
197
  }
64
198
  return root;
65
199
  }
66
200
  function readCoreManifest(templateRoot) {
67
- const path = join(templateRoot, CORE_MANIFEST_FILE);
68
- if (!existsSync(path)) {
201
+ const path = join2(templateRoot, CORE_MANIFEST_FILE);
202
+ if (!existsSync2(path)) {
69
203
  throw new Error(`${CORE_MANIFEST_FILE} not found in ${templateRoot}`);
70
204
  }
71
205
  let parsed;
72
206
  try {
73
- parsed = JSON.parse(readFileSync(path, "utf8"));
207
+ parsed = JSON.parse(readFileSync2(path, "utf8"));
74
208
  } catch (err) {
75
209
  throw new Error(`${CORE_MANIFEST_FILE} is not valid JSON: ${err.message}`);
76
210
  }
77
- const result = CoreManifestSchema.safeParse(parsed);
211
+ const result = CoreManifestSchema2.safeParse(parsed);
78
212
  if (!result.success) {
79
213
  const detail = result.error.issues[0]?.message ?? "unexpected shape";
80
214
  throw new Error(`${CORE_MANIFEST_FILE} is invalid: ${detail}`);
@@ -109,7 +243,7 @@ function listTemplateOwnedFiles(root, manifest) {
109
243
  function walk(dir) {
110
244
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
111
245
  if (entry.isDirectory() && HARD_EXCLUDED_DIRS.has(entry.name)) continue;
112
- const abs = join(dir, entry.name);
246
+ const abs = join2(dir, entry.name);
113
247
  if (entry.isDirectory()) {
114
248
  walk(abs);
115
249
  } else if (entry.isFile()) {
@@ -122,7 +256,7 @@ function listTemplateOwnedFiles(root, manifest) {
122
256
  return out.sort();
123
257
  }
124
258
  function sameFile(a, b) {
125
- return readFileSync(a).equals(readFileSync(b));
259
+ return readFileSync2(a).equals(readFileSync2(b));
126
260
  }
127
261
  function computeCoreDiff(templateRoot, instanceRoot, manifest) {
128
262
  const templateFiles = new Set(listTemplateOwnedFiles(templateRoot, manifest));
@@ -132,7 +266,7 @@ function computeCoreDiff(templateRoot, instanceRoot, manifest) {
132
266
  if (!instanceFiles.has(rel)) {
133
267
  diff.added.push(rel);
134
268
  diff.entries.push({ path: rel, kind: "added" });
135
- } else if (sameFile(join(templateRoot, rel), join(instanceRoot, rel))) {
269
+ } else if (sameFile(join2(templateRoot, rel), join2(instanceRoot, rel))) {
136
270
  diff.unchanged++;
137
271
  } else {
138
272
  diff.modified.push(rel);
@@ -148,103 +282,6 @@ function computeCoreDiff(templateRoot, instanceRoot, manifest) {
148
282
  return diff;
149
283
  }
150
284
 
151
- // src/lib/core-version.ts
152
- import { execFileSync } from "child_process";
153
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
154
- import { dirname as dirname2, join as join2, parse as parsePath } from "path";
155
- import { fileURLToPath as fileURLToPath2 } from "url";
156
- import { z as z2 } from "zod";
157
- var SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
158
- var CoreManifestSchema2 = z2.object({
159
- version: z2.string().regex(SEMVER, "must be a semver, e.g. 1.2.3")
160
- });
161
- var CORE_VERSION_FILE = "core.version";
162
- var INSTANCE_CORE_FILE = "biffo.core.json";
163
- function parseCoreVersion(raw) {
164
- const match = SEMVER.exec(raw.trim());
165
- if (!match) {
166
- throw new Error(`Invalid core version ${JSON.stringify(raw)}: expected semver like 1.2.3`);
167
- }
168
- return [Number(match[1]), Number(match[2]), Number(match[3])];
169
- }
170
- function compareCoreVersions(a, b) {
171
- const [aMaj, aMin, aPat] = parseCoreVersion(a);
172
- const [bMaj, bMin, bPat] = parseCoreVersion(b);
173
- if (aMaj !== bMaj) return aMaj < bMaj ? -1 : 1;
174
- if (aMin !== bMin) return aMin < bMin ? -1 : 1;
175
- if (aPat !== bPat) return aPat < bPat ? -1 : 1;
176
- return 0;
177
- }
178
- function readCoreVersionFile(path) {
179
- const raw = readFileSync2(path, "utf8").trim();
180
- parseCoreVersion(raw);
181
- return raw;
182
- }
183
- function findCoreVersionUpward(startDir) {
184
- let dir = startDir;
185
- for (; ; ) {
186
- const candidate = join2(dir, CORE_VERSION_FILE);
187
- if (existsSync2(candidate)) return candidate;
188
- const parent = dirname2(dir);
189
- if (parent === dir || dir === parsePath(dir).root) return null;
190
- dir = parent;
191
- }
192
- }
193
- function getLatestCoreVersion(fromDir) {
194
- const start = fromDir ?? dirname2(fileURLToPath2(import.meta.url));
195
- const path = findCoreVersionUpward(start);
196
- if (!path) {
197
- throw new Error(
198
- `Could not locate a ${CORE_VERSION_FILE} file above ${start}. This CLI build is missing its core version.`
199
- );
200
- }
201
- return readCoreVersionFile(path);
202
- }
203
- function readInstanceCoreVersion(cwd) {
204
- const path = join2(cwd, INSTANCE_CORE_FILE);
205
- if (!existsSync2(path)) {
206
- const inherited = join2(cwd, CORE_VERSION_FILE);
207
- return existsSync2(inherited) ? readCoreVersionFile(inherited) : null;
208
- }
209
- let parsed;
210
- try {
211
- parsed = JSON.parse(readFileSync2(path, "utf8"));
212
- } catch (err) {
213
- throw new Error(`${INSTANCE_CORE_FILE} is not valid JSON: ${err.message}`);
214
- }
215
- const result = CoreManifestSchema2.safeParse(parsed);
216
- if (!result.success) {
217
- const detail = result.error.issues[0]?.message ?? "unexpected shape";
218
- throw new Error(`${INSTANCE_CORE_FILE} is invalid: ${detail}`);
219
- }
220
- return result.data.version;
221
- }
222
- function serializeInstanceCoreVersion(version) {
223
- parseCoreVersion(version);
224
- return `${JSON.stringify({ version }, null, 2)}
225
- `;
226
- }
227
- function writeInstanceCoreVersion(cwd, version) {
228
- writeFileSync(join2(cwd, INSTANCE_CORE_FILE), serializeInstanceCoreVersion(version));
229
- }
230
- function latestCoreVersionFromTags(repo, git = defaultTagRunner) {
231
- try {
232
- git(["-C", repo, "fetch", "--tags", "--quiet"]);
233
- } catch {
234
- }
235
- let out;
236
- try {
237
- out = git(["-C", repo, "tag", "--list", "core-v*"]);
238
- } catch {
239
- return null;
240
- }
241
- const versions = out.split("\n").map((line) => line.trim()).filter((line) => line.startsWith(CORE_TAG_PREFIX)).map((tag) => tag.slice(CORE_TAG_PREFIX.length)).filter((v) => SEMVER.test(v));
242
- if (versions.length === 0) return null;
243
- return versions.sort(compareCoreVersions).at(-1) ?? null;
244
- }
245
- var CORE_TAG_PREFIX = "core-v";
246
- var defaultTagRunner = (args) => execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
247
-
248
285
  // src/lib/logger.ts
249
286
  import chalk from "chalk";
250
287
  var log = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.59.3",
3
+ "version": "0.60.1",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,7 +23,6 @@
23
23
  "files": [
24
24
  "dist",
25
25
  "schemas",
26
- "core.version",
27
26
  "_skeletons"
28
27
  ],
29
28
  "scripts": {
@@ -35,7 +34,7 @@
35
34
  "lint:fix": "eslint src/ --fix",
36
35
  "typecheck": "tsc --noEmit",
37
36
  "test": "vitest run",
38
- "check:core-bump": "tsx src/scripts/check-core-version-bump.ts",
37
+ "check:release-subject": "tsx src/scripts/check-release-subject.ts",
39
38
  "check:core-ownership": "tsx src/scripts/check-core-ownership.ts",
40
39
  "check:plugin-terraform": "tsx src/scripts/check-plugin-terraform.ts",
41
40
  "sync:core-tag": "tsx src/scripts/sync-core-tag.ts",
package/core.version DELETED
@@ -1 +0,0 @@
1
- 0.59.3