@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.
- package/_skeletons/plugin-template/README.md +1 -1
- package/dist/index.js +152 -115
- package/package.json +2 -3
- package/core.version +0 -1
|
@@ -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
|
|
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
|
|
27
|
+
var SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
21
28
|
var CoreManifestSchema = z.object({
|
|
22
|
-
version: z.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 (
|
|
181
|
+
if (existsSync2(join2(dir, CORE_MANIFEST_FILE)) && !existsSync2(join2(dir, INSTANCE_CORE_FILE))) {
|
|
48
182
|
return dir;
|
|
49
183
|
}
|
|
50
|
-
const parent =
|
|
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 ??
|
|
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
|
|
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 =
|
|
68
|
-
if (!
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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.
|
|
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:
|
|
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
|