@theholocron/cli 2.0.0-alpha.39 → 2.0.0-alpha.40
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/cli.mjs +90 -12
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1964,20 +1964,86 @@ const WORKFLOW_CHECK_CONTEXTS = {
|
|
|
1964
1964
|
typecheck: "Typecheck / tsc --noEmit"
|
|
1965
1965
|
};
|
|
1966
1966
|
/**
|
|
1967
|
-
* Generate the thin caller content for a workflow, optionally injecting
|
|
1968
|
-
* `with:`
|
|
1967
|
+
* Generate the thin caller content for a workflow, optionally injecting or
|
|
1968
|
+
* merging `with:` overrides into the jobs block.
|
|
1969
|
+
*
|
|
1970
|
+
* Two strategies are used depending on the template:
|
|
1971
|
+
* - Templates that already have a `with:` block (e.g. lint, sync-github):
|
|
1972
|
+
* the override entries are merged in, replacing existing keys and appending
|
|
1973
|
+
* new ones.
|
|
1974
|
+
* - Templates that end with ` secrets: inherit`: a new `with:` block is
|
|
1975
|
+
* injected immediately before `secrets: inherit`.
|
|
1976
|
+
* If neither pattern matches the template, a warning is emitted and the
|
|
1977
|
+
* base template is returned unchanged.
|
|
1969
1978
|
*/
|
|
1970
1979
|
function generateThinCallerContent(name, withOverrides) {
|
|
1971
1980
|
const base = WORKFLOW_TEMPLATES[name];
|
|
1972
1981
|
if (!base) return "";
|
|
1973
1982
|
if (!withOverrides || Object.keys(withOverrides).length === 0) return base;
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1983
|
+
const fmt = (k, v) => ` ${k}: ${v === true ? "true" : v === false ? "false" : String(v)}`;
|
|
1984
|
+
const withBlockRe = /( {4}with:\n)((?:[ ]{6}[^\n]+\n)*)/;
|
|
1985
|
+
const existingMatch = base.match(withBlockRe);
|
|
1986
|
+
if (existingMatch) {
|
|
1987
|
+
const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).map((line) => {
|
|
1988
|
+
const m = line.match(/^ {6}([^:]+):\s*(.*)/);
|
|
1989
|
+
return m ? [m[1].trim(), m[2].trim()] : null;
|
|
1990
|
+
}).filter((e) => e !== null));
|
|
1991
|
+
for (const [k, v] of Object.entries(withOverrides)) existingEntries.set(k, v === true ? "true" : v === false ? "false" : String(v));
|
|
1992
|
+
const merged = [...existingEntries.entries()].map(([k, v]) => ` ${k}: ${v}`).join("\n");
|
|
1993
|
+
return base.replace(withBlockRe, ` with:\n${merged}\n`);
|
|
1994
|
+
}
|
|
1995
|
+
const withBlock = Object.entries(withOverrides).map(([k, v]) => fmt(k, v)).join("\n");
|
|
1996
|
+
const result = base.replace(/ {4}secrets: inherit\n$/, ` with:\n${withBlock}\n secrets: inherit\n`);
|
|
1997
|
+
if (result === base) console.warn(`[generateThinCallerContent] could not inject with: overrides into "${name}" template`);
|
|
1998
|
+
return result;
|
|
1976
1999
|
}
|
|
1977
2000
|
//#endregion
|
|
1978
2001
|
//#region src/commands/sync-github.ts
|
|
1979
2002
|
const DEFAULT_REPO = "theholocron/.github";
|
|
1980
2003
|
const API_BASE = "https://api.github.com";
|
|
2004
|
+
/**
|
|
2005
|
+
* Extracts the `project.workflows` array from a `holocron.config.ts` source string.
|
|
2006
|
+
* Handles both plain string entries and `{ name, with }` object entries.
|
|
2007
|
+
* Falls back to an empty array if the array cannot be found or parsed.
|
|
2008
|
+
*/
|
|
2009
|
+
function parseWorkflowsFromTs(source) {
|
|
2010
|
+
const keyMatch = source.match(/\bworkflows\s*:\s*\[/);
|
|
2011
|
+
if (!keyMatch) return [];
|
|
2012
|
+
const start = keyMatch.index + keyMatch[0].length;
|
|
2013
|
+
let depth = 1;
|
|
2014
|
+
let i = start;
|
|
2015
|
+
while (i < source.length && depth > 0) {
|
|
2016
|
+
if (source[i] === "[") depth++;
|
|
2017
|
+
else if (source[i] === "]") depth--;
|
|
2018
|
+
i++;
|
|
2019
|
+
}
|
|
2020
|
+
const body = source.slice(start, i - 1);
|
|
2021
|
+
const entries = [];
|
|
2022
|
+
const objSpans = [];
|
|
2023
|
+
const objRe = /\{\s*name\s*:\s*"([^"]+)"(?:\s*,\s*with\s*:\s*(\{[^}]*\}))?\s*\}/g;
|
|
2024
|
+
let m;
|
|
2025
|
+
while ((m = objRe.exec(body)) !== null) {
|
|
2026
|
+
objSpans.push([m.index, m.index + m[0].length]);
|
|
2027
|
+
let withObj;
|
|
2028
|
+
if (m[2]) try {
|
|
2029
|
+
withObj = JSON.parse(m[2]);
|
|
2030
|
+
} catch {}
|
|
2031
|
+
entries.push({
|
|
2032
|
+
pos: m.index,
|
|
2033
|
+
entry: {
|
|
2034
|
+
name: m[1],
|
|
2035
|
+
...withObj && { with: withObj }
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
const strRe = /"([^"]+)"/g;
|
|
2040
|
+
while ((m = strRe.exec(body)) !== null) if (!objSpans.some(([s, e]) => m.index >= s && m.index < e)) entries.push({
|
|
2041
|
+
pos: m.index,
|
|
2042
|
+
entry: { name: m[1] }
|
|
2043
|
+
});
|
|
2044
|
+
entries.sort((a, b) => a.pos - b.pos);
|
|
2045
|
+
return entries.map(({ entry }) => entry);
|
|
2046
|
+
}
|
|
1981
2047
|
function reusableHeader(source) {
|
|
1982
2048
|
return [
|
|
1983
2049
|
`# AUTO-GENERATED — do not edit in theholocron/.github directly.`,
|
|
@@ -1998,7 +2064,7 @@ function thinCallerHeader(forPrimary = false) {
|
|
|
1998
2064
|
``
|
|
1999
2065
|
].join("\n");
|
|
2000
2066
|
}
|
|
2001
|
-
function buildBatch(repo, allowedWorkflows) {
|
|
2067
|
+
function buildBatch(repo, allowedWorkflows, withOverrides) {
|
|
2002
2068
|
const files = [];
|
|
2003
2069
|
const isPrimaryGithubRepo = repo === DEFAULT_REPO;
|
|
2004
2070
|
if (isPrimaryGithubRepo) for (const [name, content] of Object.entries(ACTIONS)) files.push({
|
|
@@ -2023,7 +2089,7 @@ function buildBatch(repo, allowedWorkflows) {
|
|
|
2023
2089
|
}
|
|
2024
2090
|
} else for (const name of Object.keys(REUSABLE_WORKFLOWS)) {
|
|
2025
2091
|
if (allowedWorkflows && !allowedWorkflows.has(name)) continue;
|
|
2026
|
-
const content = generateThinCallerContent(name);
|
|
2092
|
+
const content = generateThinCallerContent(name, withOverrides?.get(name));
|
|
2027
2093
|
if (!content) continue;
|
|
2028
2094
|
files.push({
|
|
2029
2095
|
path: `.github/workflows/${name}.yml`,
|
|
@@ -2105,15 +2171,27 @@ async function runSyncGithub(input) {
|
|
|
2105
2171
|
const { tree: existingTree } = await (await fetchFn(`${API_BASE}/repos/${owner}/${repoName}/git/trees/${baseTreeSha}?recursive=1`, { headers })).json();
|
|
2106
2172
|
const existingBlobs = new Map(existingTree.filter((i) => i.type === "blob").map((i) => [i.path, i.sha]));
|
|
2107
2173
|
let allowedWorkflows;
|
|
2174
|
+
let withOverrides;
|
|
2108
2175
|
if (repo !== DEFAULT_REPO) try {
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
const
|
|
2113
|
-
|
|
2176
|
+
let entries = [];
|
|
2177
|
+
const jsonRes = await fetchFn(`${API_BASE}/repos/${owner}/${repoName}/contents/holocron.config.json`, { headers });
|
|
2178
|
+
if (jsonRes.ok) {
|
|
2179
|
+
const data = await jsonRes.json();
|
|
2180
|
+
entries = (JSON.parse(Buffer.from(data.content.replace(/\n/g, ""), "base64").toString("utf8"))?.project?.workflows ?? []).map((w) => typeof w === "string" ? { name: w } : w);
|
|
2181
|
+
} else {
|
|
2182
|
+
const tsRes = await fetchFn(`${API_BASE}/repos/${owner}/${repoName}/contents/holocron.config.ts`, { headers });
|
|
2183
|
+
if (tsRes.ok) {
|
|
2184
|
+
const data = await tsRes.json();
|
|
2185
|
+
entries = parseWorkflowsFromTs(Buffer.from(data.content.replace(/\n/g, ""), "base64").toString("utf8"));
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
if (entries.length > 0) {
|
|
2189
|
+
allowedWorkflows = new Set(entries.map((e) => e.name));
|
|
2190
|
+
const overrideEntries = entries.filter((e) => e.with != null).map((e) => [e.name, e.with]);
|
|
2191
|
+
if (overrideEntries.length > 0) withOverrides = new Map(overrideEntries);
|
|
2114
2192
|
}
|
|
2115
2193
|
} catch {}
|
|
2116
|
-
const batch = buildBatch(repo, allowedWorkflows);
|
|
2194
|
+
const batch = buildBatch(repo, allowedWorkflows, withOverrides);
|
|
2117
2195
|
let created = 0;
|
|
2118
2196
|
let updated = 0;
|
|
2119
2197
|
let unchanged = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.40",
|
|
4
4
|
"description": "The Holocron CLI — a pluggable, capability-based orchestrator for spinning up and operating software projects.",
|
|
5
5
|
"homepage": "https://github.com/theholocron/holocron/tree/main/packages/cli#readme",
|
|
6
6
|
"bugs": "https://github.com/theholocron/holocron/issues",
|