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