@theholocron/cli 3.5.4 → 3.7.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.
- package/dist/capabilities/index.d.mts +21 -1
- package/dist/cli.mjs +329 -78
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +35 -2
- package/dist/index.mjs +9 -2
- package/package.json +3 -2
|
@@ -157,6 +157,26 @@ interface Source extends ProviderIdentity {
|
|
|
157
157
|
* Optional — providers that don't support setting a homepage omit this.
|
|
158
158
|
*/
|
|
159
159
|
syncHomepage?(homepage: string): Promise<string>;
|
|
160
|
+
/**
|
|
161
|
+
* Enable or update GitHub Pages for the repository.
|
|
162
|
+
* Idempotent: POST to create, PUT to update existing settings.
|
|
163
|
+
* Requires HOLOCRON_DEPLOY_TOKEN (pages:write + repo scope) — passed
|
|
164
|
+
* explicitly because it is a different PAT from the main admin token.
|
|
165
|
+
* Optional — providers without a Pages concept omit this.
|
|
166
|
+
*/
|
|
167
|
+
configurePages?(config: PagesConfig, token: string): Promise<void>;
|
|
168
|
+
}
|
|
169
|
+
interface PagesConfig {
|
|
170
|
+
/** GitHub Pages build source. */
|
|
171
|
+
build: "workflow" | "branch";
|
|
172
|
+
/**
|
|
173
|
+
* Custom domain / CNAME (e.g. "docs.theholocron.dev").
|
|
174
|
+
* When set and `homepage` is absent in holocron.config, setup derives
|
|
175
|
+
* homepage as `https://{domain}/`.
|
|
176
|
+
*/
|
|
177
|
+
domain?: string;
|
|
178
|
+
/** Enforce HTTPS. Only effective once the custom domain is DNS-verified. */
|
|
179
|
+
https?: boolean;
|
|
160
180
|
}
|
|
161
181
|
type CiRunStatus = "queued" | "in_progress" | "completed" | "cancelled" | "failure" | "success" | "skipped";
|
|
162
182
|
interface CiRun {
|
|
@@ -571,4 +591,4 @@ type CardinalityFor<K extends CapabilityKey> = (typeof CARDINALITY)[K];
|
|
|
571
591
|
type ResolvedCapability<K extends CapabilityKey> = CardinalityFor<K> extends "many" ? CapabilityImpls[K][] : CapabilityImpls[K];
|
|
572
592
|
declare function isMulti<K extends CapabilityKey>(key: K): CardinalityFor<K> extends "many" ? true : false;
|
|
573
593
|
//#endregion
|
|
574
|
-
export { Analytics, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, EnsureResult, Environment, EnvironmentReviewer, Environments, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, REQUIRED_CAPABILITIES, RepoRef, RepoSettings, ResolvedCapability, Ruleset, SecretScope, Secrets, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, isMulti };
|
|
594
|
+
export { Analytics, Auth, AuthDescription, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, EnsureResult, Environment, EnvironmentReviewer, Environments, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, NormalizedAuthUser, Notifications, Observability, PagesConfig, ParseWebhookInput, ProviderApiError, ProviderIdentity, REQUIRED_CAPABILITIES, RepoRef, RepoSettings, ResolvedCapability, Ruleset, SecretScope, Secrets, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, isMulti };
|
package/dist/cli.mjs
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
import { createInterface } from "node:readline";
|
|
5
|
+
import { input, select } from "@inquirer/prompts";
|
|
7
6
|
import yargs from "yargs";
|
|
8
7
|
import { hideBin } from "yargs/helpers";
|
|
9
8
|
import { AuthError, ProviderApiError, ProviderApiError as ProviderApiError$1 } from "@theholocron/http-client";
|
|
@@ -248,17 +247,24 @@ function resolveConfig(raw) {
|
|
|
248
247
|
providers[key] = resolveEntry(key, entry);
|
|
249
248
|
}
|
|
250
249
|
for (const required of REQUIRED_CAPABILITIES) if (!providers[required]) throw new ConfigError(`required capability \`${required}\` is missing from providers`);
|
|
250
|
+
let docs;
|
|
251
|
+
if (raw.docs !== void 0) {
|
|
252
|
+
if (!raw.docs.build) throw new ConfigError("`docs.build` is required when `docs` is set");
|
|
253
|
+
docs = raw.docs;
|
|
254
|
+
}
|
|
255
|
+
const homepage = raw.homepage ?? (docs?.domain ? `https://${docs.domain}/` : void 0);
|
|
251
256
|
return {
|
|
252
257
|
name: raw.name,
|
|
253
258
|
description: raw.description,
|
|
254
|
-
homepage
|
|
259
|
+
homepage,
|
|
255
260
|
repo: raw.repo,
|
|
256
261
|
workflows: raw.workflows,
|
|
257
262
|
providers,
|
|
258
263
|
apps: raw.apps ?? [],
|
|
259
264
|
doctor: raw.doctor ?? {},
|
|
260
265
|
agent: raw.agent,
|
|
261
|
-
skills: raw.skills
|
|
266
|
+
skills: raw.skills,
|
|
267
|
+
docs
|
|
262
268
|
};
|
|
263
269
|
}
|
|
264
270
|
//#endregion
|
|
@@ -810,19 +816,21 @@ function pad(s, width) {
|
|
|
810
816
|
/**
|
|
811
817
|
* `holocron new <type> <name>` — create a GitHub repo from a template and
|
|
812
818
|
* bootstrap it by replacing all template-slug casing variants with the new
|
|
813
|
-
* project name.
|
|
819
|
+
* project name, then generate a `holocron.config.ts` from the answers given
|
|
820
|
+
* during the interactive wizard.
|
|
814
821
|
*
|
|
815
822
|
* Flow:
|
|
816
823
|
* 1. Preflight — verify `gh` CLI is available.
|
|
817
|
-
* 2. Resolve type, name, description
|
|
824
|
+
* 2. Resolve type, name, description, homepage, vault/deployment/agent options.
|
|
818
825
|
* 3. `gh repo create <org>/<name> --template <org>/<type>-template --private --clone`
|
|
819
826
|
* → clones to `<cwd>/<name>/`
|
|
820
827
|
* 4. Detect template slug from cloned package.json.
|
|
821
828
|
* 5. Replace all casing variants of the slug across every text file.
|
|
822
|
-
* 6. Replace `<description>`
|
|
823
|
-
* 7.
|
|
824
|
-
* 8.
|
|
825
|
-
* 9.
|
|
829
|
+
* 6. Replace `<description>` and `<homepage>` placeholders.
|
|
830
|
+
* 7. Generate and write `holocron.config.ts` based on wizard answers.
|
|
831
|
+
* 8. Commit the patched files (-s for DCO).
|
|
832
|
+
* 9. Unless --no-verify: `pnpm install` in the new repo.
|
|
833
|
+
* 10. Print next steps.
|
|
826
834
|
*/
|
|
827
835
|
var NewError = class extends Error {
|
|
828
836
|
name = "NewError";
|
|
@@ -856,6 +864,63 @@ function deriveVariants(slug, name) {
|
|
|
856
864
|
return true;
|
|
857
865
|
});
|
|
858
866
|
}
|
|
867
|
+
/** Validate a repo name: must be lowercase kebab-case. Returns `true` on success or an error string. */
|
|
868
|
+
function validateRepoName(v) {
|
|
869
|
+
return /^[a-z][a-z0-9-]*$/.test(v.trim()) ? true : "Must be lowercase kebab-case (e.g. my-tool)";
|
|
870
|
+
}
|
|
871
|
+
/** Parse a comma-separated topics string into a trimmed, non-empty array. */
|
|
872
|
+
function parseTopics(raw) {
|
|
873
|
+
return raw ? String(raw).split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Generate the content of `holocron.config.ts` for a newly-scaffolded repo.
|
|
877
|
+
* Uses the `node()` preset from `@theholocron/holocron-config` as the baseline
|
|
878
|
+
* and layers in the provider/agent choices made during the wizard.
|
|
879
|
+
*/
|
|
880
|
+
function generateHolocronConfig(opts) {
|
|
881
|
+
const lines = [
|
|
882
|
+
`import { defineConfig } from "@theholocron/cli";`,
|
|
883
|
+
`import { node } from "@theholocron/holocron-config";`,
|
|
884
|
+
``,
|
|
885
|
+
`const { repo, workflows, providers } = node();`,
|
|
886
|
+
`export default defineConfig({`
|
|
887
|
+
];
|
|
888
|
+
if (opts.description) lines.push(`\tdescription: ${JSON.stringify(opts.description)},`);
|
|
889
|
+
if (opts.homepage) lines.push(`\thomepage: ${JSON.stringify(opts.homepage)},`);
|
|
890
|
+
const topics = opts.topics?.length ? opts.topics : [];
|
|
891
|
+
const hasRuntimeOverride = opts.runtimeEnvironment != null && opts.runtimeEnvironment !== "node";
|
|
892
|
+
if (topics.length > 0 || hasRuntimeOverride) {
|
|
893
|
+
lines.push(`\trepo: {`);
|
|
894
|
+
if (topics.length > 0) lines.push(`\t\ttopics: ${JSON.stringify(topics)},`);
|
|
895
|
+
lines.push(`\t\t...repo,`);
|
|
896
|
+
if (hasRuntimeOverride) lines.push(`\t\tproperties: { ...repo.properties, runtime_environment: ${JSON.stringify(opts.runtimeEnvironment)} },`);
|
|
897
|
+
lines.push(`\t},`);
|
|
898
|
+
} else lines.push(`\trepo,`);
|
|
899
|
+
lines.push(`\tworkflows,`);
|
|
900
|
+
const hasVault = opts.vaultProvider && opts.vaultProvider !== "none";
|
|
901
|
+
const hasDeployment = opts.deploymentProvider && opts.deploymentProvider !== "none";
|
|
902
|
+
if (hasVault || hasDeployment) {
|
|
903
|
+
lines.push(`\tproviders: {`);
|
|
904
|
+
lines.push(`\t\t...providers,`);
|
|
905
|
+
if (opts.vaultProvider === "doppler") {
|
|
906
|
+
const proj = JSON.stringify(opts.vaultProject ?? opts.name);
|
|
907
|
+
const cfg = JSON.stringify(opts.vaultConfig ?? "dev");
|
|
908
|
+
lines.push(`\t\tvault: ["doppler", { project: ${proj}, config: ${cfg} }],`);
|
|
909
|
+
} else if (opts.vaultProvider === "1password") {
|
|
910
|
+
const vault = JSON.stringify(opts.vaultProject ?? opts.name);
|
|
911
|
+
lines.push(`\t\tvault: ["1password", { vault: ${vault} }],`);
|
|
912
|
+
} else if (opts.vaultProvider === "infisical") {
|
|
913
|
+
const proj = JSON.stringify(opts.vaultProject ?? opts.name);
|
|
914
|
+
lines.push(`\t\tvault: ["infisical", { project: ${proj} }],`);
|
|
915
|
+
}
|
|
916
|
+
if (opts.deploymentProvider === "vercel") lines.push(`\t\tdeployment: "vercel",`);
|
|
917
|
+
lines.push(`\t},`);
|
|
918
|
+
} else lines.push(`\tproviders,`);
|
|
919
|
+
if (opts.agent && opts.agent !== "none") lines.push(`\tagent: ${JSON.stringify(opts.agent)},`);
|
|
920
|
+
lines.push(`});`);
|
|
921
|
+
lines.push(``);
|
|
922
|
+
return lines.join("\n");
|
|
923
|
+
}
|
|
859
924
|
const SKIP_DIRS$1 = /* @__PURE__ */ new Set([
|
|
860
925
|
".git",
|
|
861
926
|
"node_modules",
|
|
@@ -877,7 +942,7 @@ function isBinary(content) {
|
|
|
877
942
|
for (let i = 0; i < Math.min(content.length, 8e3); i++) if (content.charCodeAt(i) === 0) return true;
|
|
878
943
|
return false;
|
|
879
944
|
}
|
|
880
|
-
function patchFiles(dir, variants, description, print, readFn, writeFn, walkFn) {
|
|
945
|
+
function patchFiles(dir, variants, description, homepage, runtimeEnvironment, print, readFn, writeFn, walkFn) {
|
|
881
946
|
const patched = [];
|
|
882
947
|
for (const filepath of walkFn(dir)) {
|
|
883
948
|
let content;
|
|
@@ -890,6 +955,8 @@ function patchFiles(dir, variants, description, print, readFn, writeFn, walkFn)
|
|
|
890
955
|
const original = content;
|
|
891
956
|
for (const [search, replacement] of variants) content = content.split(search).join(replacement);
|
|
892
957
|
if (description !== void 0) content = content.split("<description>").join(description);
|
|
958
|
+
if (homepage !== void 0) content = content.split("<homepage>").join(homepage);
|
|
959
|
+
if (runtimeEnvironment !== void 0) content = content.split("<runtime_environment>").join(runtimeEnvironment);
|
|
893
960
|
if (content !== original) {
|
|
894
961
|
writeFn(filepath, content);
|
|
895
962
|
print(` ✓ ${path.relative(dir, filepath)}`);
|
|
@@ -932,6 +999,9 @@ async function runNew(input) {
|
|
|
932
999
|
print(` Would clone to ${repoDir}`);
|
|
933
1000
|
print(` Would patch all casing variants of "${input.type}-template" → "${input.name}"`);
|
|
934
1001
|
if (input.description) print(` Would replace <description> → "${input.description}"`);
|
|
1002
|
+
if (input.homepage) print(` Would replace <homepage> → "${input.homepage}"`);
|
|
1003
|
+
if (input.runtimeEnvironment) print(` Would replace <runtime_environment> → "${input.runtimeEnvironment}"`);
|
|
1004
|
+
print(` Would generate holocron.config.ts`);
|
|
935
1005
|
return { status: "dry-run" };
|
|
936
1006
|
}
|
|
937
1007
|
if (existsSync(repoDir)) throw new NewError(`\`${repoDir}\` already exists — delete it or pick a different name.`);
|
|
@@ -955,27 +1025,40 @@ async function runNew(input) {
|
|
|
955
1025
|
const pkgJsonPath = path.join(repoDir, "package.json");
|
|
956
1026
|
if (existsSync(pkgJsonPath)) try {
|
|
957
1027
|
const pkg = JSON.parse(readFn(pkgJsonPath));
|
|
958
|
-
if (typeof pkg.name === "string") templateSlug = pkg.name.split("/").
|
|
1028
|
+
if (typeof pkg.name === "string") templateSlug = pkg.name.split("/").at(-1) || templateSlug;
|
|
959
1029
|
} catch {}
|
|
960
1030
|
print(` Detected template slug: ${templateSlug}`);
|
|
961
1031
|
print(` Patching files…`);
|
|
962
|
-
const filesPatched = patchFiles(repoDir, deriveVariants(templateSlug, input.name), input.description, print, readFn, writeFn, walkFn);
|
|
1032
|
+
const filesPatched = patchFiles(repoDir, deriveVariants(templateSlug, input.name), input.description, input.homepage, input.runtimeEnvironment, print, readFn, writeFn, walkFn);
|
|
963
1033
|
print(` ${filesPatched.length} file${filesPatched.length === 1 ? "" : "s"} patched`);
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1034
|
+
const configContent = generateHolocronConfig({
|
|
1035
|
+
name: input.name,
|
|
1036
|
+
type: input.type,
|
|
1037
|
+
description: input.description,
|
|
1038
|
+
homepage: input.homepage,
|
|
1039
|
+
vaultProvider: input.vaultProvider,
|
|
1040
|
+
vaultProject: input.vaultProject,
|
|
1041
|
+
vaultConfig: input.vaultConfig,
|
|
1042
|
+
deploymentProvider: input.deploymentProvider,
|
|
1043
|
+
agent: input.agent,
|
|
1044
|
+
runtimeEnvironment: input.runtimeEnvironment,
|
|
1045
|
+
topics: input.topics
|
|
1046
|
+
});
|
|
1047
|
+
writeFn(path.join(repoDir, "holocron.config.ts"), configContent);
|
|
1048
|
+
print(` Generated holocron.config.ts`);
|
|
1049
|
+
execFn("git", ["add", "-A"], {
|
|
1050
|
+
cwd: repoDir,
|
|
1051
|
+
stdio: "inherit"
|
|
1052
|
+
});
|
|
1053
|
+
execFn("git", [
|
|
1054
|
+
"commit",
|
|
1055
|
+
"-s",
|
|
1056
|
+
"-m",
|
|
1057
|
+
`chore: bootstrap from ${templateSlug}`
|
|
1058
|
+
], {
|
|
1059
|
+
cwd: repoDir,
|
|
1060
|
+
stdio: "inherit"
|
|
1061
|
+
});
|
|
979
1062
|
if (!input.noVerify) {
|
|
980
1063
|
print("");
|
|
981
1064
|
print(" Installing dependencies…");
|
|
@@ -993,16 +1076,27 @@ async function runNew(input) {
|
|
|
993
1076
|
message: "pnpm install failed; inspect output above"
|
|
994
1077
|
};
|
|
995
1078
|
}
|
|
1079
|
+
print("");
|
|
1080
|
+
print(" Running holocron setup…");
|
|
1081
|
+
try {
|
|
1082
|
+
execFn("holocron", ["setup"], {
|
|
1083
|
+
cwd: repoDir,
|
|
1084
|
+
stdio: "inherit"
|
|
1085
|
+
});
|
|
1086
|
+
} catch {
|
|
1087
|
+
print(" ✗ holocron setup failed — run it manually after checking your config");
|
|
1088
|
+
}
|
|
996
1089
|
}
|
|
997
1090
|
print("");
|
|
998
1091
|
print(` Scaffolded ${newRepo} (${filesPatched.length} file${filesPatched.length === 1 ? "" : "s"} patched).`);
|
|
999
1092
|
print("");
|
|
1000
1093
|
print(" Next:");
|
|
1001
1094
|
print(` 1. cd ${repoDir}`);
|
|
1002
|
-
if (input.noVerify)
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1095
|
+
if (input.noVerify) {
|
|
1096
|
+
print(` 2. pnpm install`);
|
|
1097
|
+
print(` 3. holocron setup # wire up secrets, teams, labels, etc.`);
|
|
1098
|
+
print(` 4. git push -u origin HEAD`);
|
|
1099
|
+
} else print(` 2. git push -u origin HEAD`);
|
|
1006
1100
|
return {
|
|
1007
1101
|
status: "ok",
|
|
1008
1102
|
repoDir,
|
|
@@ -2070,6 +2164,18 @@ export default defineConfig({
|
|
|
2070
2164
|
* pnpm --filter <pkg> typecheck lint test.
|
|
2071
2165
|
* 6. Print next steps.
|
|
2072
2166
|
*/
|
|
2167
|
+
/**
|
|
2168
|
+
* Resolve `capability`, `vendorEnv`, and `baseUrl` from argv or by calling
|
|
2169
|
+
* the supplied prompt functions for any that are absent. Extracted from
|
|
2170
|
+
* `cli.ts` so the resolution logic is unit-testable.
|
|
2171
|
+
*/
|
|
2172
|
+
async function resolvePluginCreateInputs(args, prompts) {
|
|
2173
|
+
return {
|
|
2174
|
+
capability: args.capability ?? await prompts.selectCapability(),
|
|
2175
|
+
vendorEnv: args.vendorEnv ?? await prompts.inputVendorEnv(),
|
|
2176
|
+
baseUrl: args.baseUrl ?? await prompts.inputBaseUrl()
|
|
2177
|
+
};
|
|
2178
|
+
}
|
|
2073
2179
|
var PluginCreateError = class extends Error {
|
|
2074
2180
|
name = "PluginCreateError";
|
|
2075
2181
|
};
|
|
@@ -3173,6 +3279,29 @@ async function runSetup(input) {
|
|
|
3173
3279
|
print(formatStep(steps[steps.length - 1]));
|
|
3174
3280
|
}
|
|
3175
3281
|
}
|
|
3282
|
+
if (loader.has("source") && config.docs) {
|
|
3283
|
+
const source = loader.get("source");
|
|
3284
|
+
const deployToken = process.env.HOLOCRON_DEPLOY_TOKEN;
|
|
3285
|
+
print(style.step("docs"));
|
|
3286
|
+
if (!deployToken) {
|
|
3287
|
+
steps.push({
|
|
3288
|
+
capability: "source",
|
|
3289
|
+
step: "configure GitHub Pages",
|
|
3290
|
+
status: "skip",
|
|
3291
|
+
message: "HOLOCRON_DEPLOY_TOKEN not set — skipping Pages setup"
|
|
3292
|
+
});
|
|
3293
|
+
print(formatStep(steps[steps.length - 1]));
|
|
3294
|
+
} else if (source.configurePages) {
|
|
3295
|
+
steps.push(await runStep("source", "configure GitHub Pages", dryRun, async () => {
|
|
3296
|
+
await source.configurePages(config.docs, deployToken);
|
|
3297
|
+
const parts = [config.docs.build];
|
|
3298
|
+
if (config.docs.domain) parts.push(`domain: ${config.docs.domain}`);
|
|
3299
|
+
if (config.docs.https) parts.push("https: enforced");
|
|
3300
|
+
return parts.join(", ");
|
|
3301
|
+
}));
|
|
3302
|
+
print(formatStep(steps[steps.length - 1]));
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3176
3305
|
if (loader.has("deployment")) {
|
|
3177
3306
|
const deploy = loader.get("deployment");
|
|
3178
3307
|
print(style.step("deployment"));
|
|
@@ -3916,7 +4045,7 @@ var lint_default = "name: Lint\n\non: # yamllint disable-line rule:truthy\n wor
|
|
|
3916
4045
|
var release_default = "name: Release\n\n# Semantic-release with OIDC Trusted Publishing.\n# actions/setup-node writes a default NODE_AUTH_TOKEN=${{ github.token }}\n# which shadows OIDC auth. We explicitly clear it so npm falls through to\n# the Trusted Publisher OIDC exchange.\n# The calling repo must have a .releaserc.json that configures branches,\n# plugins, and any publish options. npm@11+ is installed to support OIDC.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n run-build:\n description: Run `pnpm build` before releasing\n type: boolean\n required: false\n default: true\n sentry-project:\n description: >\n Sentry project slug for sourcemap upload and release creation after\n publishing. Omit to skip the Sentry release step entirely.\n type: string\n required: false\n default: \"\"\n secrets:\n HOLOCRON_RELEASE_TOKEN:\n description: >\n Fine-grained PAT (Contents + Issues + Pull requests: write) owned by\n an admin. Required when the default branch is protected by a ruleset —\n github.token cannot push through rulesets, but an admin PAT can.\n Takes priority over HOLOCRON_SYNC_TOKEN. Falls back to github.token.\n required: false\n HOLOCRON_SYNC_TOKEN:\n description: >\n Legacy alias for HOLOCRON_RELEASE_TOKEN — kept for backward compatibility.\n Prefer HOLOCRON_RELEASE_TOKEN for new repos.\n required: false\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to github.token.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when\n HOLOCRON_READ_TOKEN is not set.\n required: false\n SENTRY_AUTH_TOKEN:\n description: >\n Sentry auth token for sourcemap upload and release creation.\n Required when sentry-project is set. Use the org-level secret.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n release:\n name: Semantic release\n permissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n # Do not cancel in-progress releases — a partial release is worse than a slow one.\n concurrency:\n group: release-${{ github.ref }}\n cancel-in-progress: false\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n persist-credentials: false\n # Use HOLOCRON_RELEASE_TOKEN when available — git push (tags, release commits)\n # uses the checkout credential, not GITHUB_TOKEN env var. The\n # built-in github.token cannot push through branch protection rulesets.\n token: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - name: Configure git identity\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n git config --global user.name \"$GIT_NAME\"\n git config --global user.email \"$GIT_EMAIL\"\n git config --global format.signoff true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.GH_TOKEN || github.token }}\n\n - name: Upgrade npm for OIDC support\n run: npm install -g npm@11 sigstore\n # sigstore is required by libnpmpublish/provenance.js at module parse\n # time — before any config takes effect. Some npm 11.x builds stopped\n # bundling it; installing it globally into the same prefix ensures it\n # resolves regardless of npm version. (Discovered 2026-07-09.)\n\n - run: pnpm build\n name: Build\n if: ${{ inputs.run-build == true }}\n\n - run: npx semantic-release\n name: Release\n env:\n # Prefer HOLOCRON_RELEASE_TOKEN (fine-grained PAT, Contents+Issues+PRs write,\n # owned by an admin with ruleset bypass) so @semantic-release/git can\n # push the version-bump commit through branch protection. Falls back to\n # HOLOCRON_SYNC_TOKEN (legacy) then github.token for unprotected repos.\n GITHUB_TOKEN: ${{ secrets.HOLOCRON_RELEASE_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || github.token }}\n HUSKY: \"0\"\n NPM_CONFIG_PROVENANCE: true\n\n - name: Get release version\n id: release_version\n if: ${{ inputs.sentry-project != '' }}\n env:\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n run: |\n TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo \"\")\n if [ -n \"$TAG\" ]; then\n echo \"release=${SENTRY_PROJECT}@${TAG#v}\" >> \"$GITHUB_OUTPUT\"\n fi\n\n - name: Create Sentry release\n if: ${{ inputs.sentry-project != '' && steps.release_version.outputs.release != '' }}\n uses: getsentry/action-release@ff07929a6537bac57790c3451cf4d364aca38528 # v3\n env:\n SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}\n SENTRY_ORG: theholocron\n SENTRY_PROJECT: ${{ inputs.sentry-project }}\n with:\n environment: production\n version: ${{ steps.release_version.outputs.release }}\n sourcemaps: \"**/dist\"\n";
|
|
3917
4046
|
//#endregion
|
|
3918
4047
|
//#region src/templates/workflows/review.yml
|
|
3919
|
-
var review_default = "name: Review\n\n# ReviewDog is the annotation layer — posts inline PR diff annotations.\n# Runs on pull_request only: inline annotations require PR context,\n# and branch protection ensures all changes go through PRs anyway.\n# super-linter (lint.yml) is the CI gate covering push + PR events.\n# Gitleaks and YAML are intentionally duplicated: super-linter gates\n# merges; ReviewDog surfaces exact line annotations in the PR diff.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n\nconcurrency:\n group: review-${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: true\n\njobs:\n reviewdog:\n name: Review PRs\n runs-on: ubuntu-latest\n timeout-minutes: 20\n permissions:\n contents: read\n pull-requests: write\n\n steps:\n - name: Checkout repository\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n\n - name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n uses: theholocron/.github/.github/actions/setup@main\n\n - name: Install ReviewDog\n uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1\n with:\n reviewdog_version: latest\n\n # Detect which tools are relevant for this repo, excluding node_modules.\n # hashFiles('**/*') recurses into node_modules/.pnpm and produces false\n # positives for repos that don't own those file types.\n # -print -quit stops find after the first match without a pipe, avoiding\n # the SIGPIPE/pipefail exit-141 that find|head-1 triggers under\n # GitHub Actions' default bash --noprofile --norc -e -o pipefail mode.\n - name: Detect project features\n id: detect\n shell: bash\n run: |\n has() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n has_ext() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n { { has 'eslint.config.js' || has 'eslint.config.mjs' || has 'eslint.config.cjs' || \\\n has 'eslint.config.ts' || has '.eslintrc' || has '.eslintrc.js' || \\\n has '.eslintrc.cjs' || has '.eslintrc.json' || has '.eslintrc.yaml' || \\\n has '.eslintrc.yml'; } && grep -qF '\"eslint\":' package.json 2>/dev/null; } && echo \"eslint=true\" >> \"$GITHUB_OUTPUT\" || echo \"eslint=false\" >> \"$GITHUB_OUTPUT\"\n { has 'tsconfig.json' && grep -qF '\"typescript\":' package.json 2>/dev/null; } && echo \"tsconfig=true\" >> \"$GITHUB_OUTPUT\" || echo \"tsconfig=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.sh' && echo \"shell=true\" >> \"$GITHUB_OUTPUT\" || echo \"shell=false\" >> \"$GITHUB_OUTPUT\"\n has 'Dockerfile' || has_ext '*.Dockerfile' || has 'Containerfile' && \\\n echo \"docker=true\" >> \"$GITHUB_OUTPUT\" || echo \"docker=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '.env*' && echo \"dotenv=true\" >> \"$GITHUB_OUTPUT\" || echo \"dotenv=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.md' && echo \"markdown=true\" >> \"$GITHUB_OUTPUT\" || echo \"markdown=false\" >> \"$GITHUB_OUTPUT\"\n\n #\n # Always applicable\n #\n\n - name: Gitleaks (secrets)\n uses: reviewdog/action-gitleaks@2b7b5685e3e3eecddab5d30cfa04f18123031421 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / gitleaks\"\n gitleaks_flags: --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}\n\n - name: YamlLint\n if: ${{ hashFiles('yamllint.config.yml') != '' }}\n uses: reviewdog/action-yamllint@b5f7217d8c815ae374d1d55840d5e569d82f01f0 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / yamllint\"\n yamllint_flags: -c ${{ github.workspace }}/yamllint.config.yml ${{ github.workspace }}\n\n - name: ActionLint (GitHub Actions)\n if: ${{ hashFiles('.github/workflows/*.yml', '.github/workflows/*.yaml') != '' }}\n uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / actionlint\"\n\n #\n # TypeScript / JavaScript\n #\n\n - name: ESLint\n if: steps.detect.outputs.eslint == 'true'\n uses: reviewdog/action-eslint@556a3fdaf8b4201d4d74d406013386aa4f7dab96 # v1.34.0\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / eslint\"\n eslint_flags: .\n\n - name: TypeScript\n if: steps.detect.outputs.tsconfig == 'true'\n uses: EPMatt/reviewdog-action-tsc@63d923a3c5b4497671940b8874f58a404e2351b5 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / tsc\"\n\n #\n # Shell\n #\n\n - name: ShellCheck\n if: steps.detect.outputs.shell == 'true'\n uses: reviewdog/action-shellcheck@4c07458293ac342d477251099501a718ae5ef86e # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / shellcheck\"\n fail_level: none\n\n #\n # Docker\n #\n\n - name: Hadolint\n if: steps.detect.outputs.docker == 'true'\n uses: reviewdog/action-hadolint@1b2cfa6ba72072ad35158d7ff3aa49bbdc03506d # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / hadolint\"\n fail_level: none\n\n #\n # Environment files\n #\n\n - name: dotenv-linter\n if: steps.detect.outputs.dotenv == 'true'\n uses: dotenv-linter/action-dotenv-linter@afde61cfda2ecffe7bea35837b6f20b956c88689 # v3.0.0\n with:\n reporter: github-code-suggestions\n\n #\n # Documentation\n #\n\n - name: Alex (inclusive language)\n if: steps.detect.outputs.markdown == 'true'\n uses: reviewdog/action-alex@347481655add010a2ae302df34b57c9bcfa0d6e4 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / alex\"\n";
|
|
4048
|
+
var review_default = "name: Review\n\n# ReviewDog is the annotation layer — posts inline PR diff annotations.\n# Runs on pull_request only: inline annotations require PR context,\n# and branch protection ensures all changes go through PRs anyway.\n# super-linter (lint.yml) is the CI gate covering push + PR events.\n# Gitleaks and YAML are intentionally duplicated: super-linter gates\n# merges; ReviewDog surfaces exact line annotations in the PR diff.\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n\nconcurrency:\n group: review-${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: true\n\njobs:\n reviewdog:\n name: Review PRs\n runs-on: ubuntu-latest\n timeout-minutes: 20\n permissions:\n contents: read\n pull-requests: write\n\n steps:\n - name: Checkout repository\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n\n - name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n uses: theholocron/.github/.github/actions/setup@main\n\n - name: Install ReviewDog\n uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1\n with:\n reviewdog_version: latest\n\n # Detect which tools are relevant for this repo, excluding node_modules.\n # hashFiles('**/*') recurses into node_modules/.pnpm and produces false\n # positives for repos that don't own those file types.\n # -print -quit stops find after the first match without a pipe, avoiding\n # the SIGPIPE/pipefail exit-141 that find|head-1 triggers under\n # GitHub Actions' default bash --noprofile --norc -e -o pipefail mode.\n - name: Detect project features\n id: detect\n shell: bash\n run: |\n has() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n has_ext() { find . -not -path '*/node_modules/*' -name \"$1\" -print -quit 2>/dev/null | grep -q .; }\n { { has 'eslint.config.js' || has 'eslint.config.mjs' || has 'eslint.config.cjs' || \\\n has 'eslint.config.ts' || has '.eslintrc' || has '.eslintrc.js' || \\\n has '.eslintrc.cjs' || has '.eslintrc.json' || has '.eslintrc.yaml' || \\\n has '.eslintrc.yml'; } && grep -qF '\"eslint\":' package.json 2>/dev/null; } && echo \"eslint=true\" >> \"$GITHUB_OUTPUT\" || echo \"eslint=false\" >> \"$GITHUB_OUTPUT\"\n { has 'tsconfig.json' && grep -qF '\"typescript\":' package.json 2>/dev/null; } && echo \"tsconfig=true\" >> \"$GITHUB_OUTPUT\" || echo \"tsconfig=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.sh' && echo \"shell=true\" >> \"$GITHUB_OUTPUT\" || echo \"shell=false\" >> \"$GITHUB_OUTPUT\"\n has 'Dockerfile' || has_ext '*.Dockerfile' || has 'Containerfile' && \\\n echo \"docker=true\" >> \"$GITHUB_OUTPUT\" || echo \"docker=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '.env*' && echo \"dotenv=true\" >> \"$GITHUB_OUTPUT\" || echo \"dotenv=false\" >> \"$GITHUB_OUTPUT\"\n has_ext '*.md' && echo \"markdown=true\" >> \"$GITHUB_OUTPUT\" || echo \"markdown=false\" >> \"$GITHUB_OUTPUT\"\n\n #\n # Always applicable\n #\n\n - name: Gitleaks (secrets)\n uses: reviewdog/action-gitleaks@2b7b5685e3e3eecddab5d30cfa04f18123031421 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / gitleaks\"\n fail_level: error\n gitleaks_flags: --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}\n\n - name: YamlLint\n if: ${{ hashFiles('yamllint.config.yml') != '' }}\n uses: reviewdog/action-yamllint@b5f7217d8c815ae374d1d55840d5e569d82f01f0 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / yamllint\"\n fail_level: error\n yamllint_flags: -c ${{ github.workspace }}/yamllint.config.yml ${{ github.workspace }}\n\n - name: ActionLint (GitHub Actions)\n if: ${{ hashFiles('.github/workflows/*.yml', '.github/workflows/*.yaml') != '' }}\n uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / actionlint\"\n fail_level: error\n\n #\n # TypeScript / JavaScript\n #\n\n - name: ESLint\n if: steps.detect.outputs.eslint == 'true'\n uses: reviewdog/action-eslint@556a3fdaf8b4201d4d74d406013386aa4f7dab96 # v1.34.0\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / eslint\"\n fail_level: error\n eslint_flags: .\n\n - name: TypeScript\n if: steps.detect.outputs.tsconfig == 'true'\n uses: EPMatt/reviewdog-action-tsc@63d923a3c5b4497671940b8874f58a404e2351b5 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / tsc\"\n fail_level: error\n\n #\n # Shell\n #\n\n - name: ShellCheck\n if: steps.detect.outputs.shell == 'true'\n uses: reviewdog/action-shellcheck@4c07458293ac342d477251099501a718ae5ef86e # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / shellcheck\"\n fail_level: none\n\n #\n # Docker\n #\n\n - name: Hadolint\n if: steps.detect.outputs.docker == 'true'\n uses: reviewdog/action-hadolint@1b2cfa6ba72072ad35158d7ff3aa49bbdc03506d # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / hadolint\"\n fail_level: none\n\n #\n # Environment files\n #\n\n - name: dotenv-linter\n if: steps.detect.outputs.dotenv == 'true'\n uses: dotenv-linter/action-dotenv-linter@afde61cfda2ecffe7bea35837b6f20b956c88689 # v3.0.0\n with:\n reporter: github-code-suggestions\n\n #\n # Documentation\n #\n\n - name: Alex (inclusive language)\n if: steps.detect.outputs.markdown == 'true'\n uses: reviewdog/action-alex@347481655add010a2ae302df34b57c9bcfa0d6e4 # v1\n with:\n reporter: github-pr-annotations\n tool_name: \"Review / alex\"\n";
|
|
3920
4049
|
//#endregion
|
|
3921
4050
|
//#region src/templates/workflows/stale.yml
|
|
3922
4051
|
var stale_default = "name: Stale\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n days-before-stale:\n description: Days of inactivity before an issue is marked stale\n type: number\n required: false\n default: 30\n days-before-close:\n description: Days of inactivity after stale label before closing\n type: number\n required: false\n default: 5\n\njobs:\n stale:\n name: Mark stale issues and pull requests\n permissions:\n contents: write\n issues: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0\n name: Run Stale\n with:\n close-issue-message: >\n This issue was closed because it has been stalled for\n ${{ inputs.days-before-close }} days with no activity.\n days-before-close: ${{ inputs.days-before-close }}\n days-before-stale: ${{ inputs.days-before-stale }}\n exempt-all-pr-milestones: true\n stale-issue-label: wontfix\n stale-issue-message: >\n This issue is stale because it has been open ${{ inputs.days-before-stale }}\n days with no activity. Remove the stale label or comment, or this will be\n closed in ${{ inputs.days-before-close }} days.\n stale-pr-label: wontfix\n stale-pr-message: >\n This PR is stale because it has been open ${{ inputs.days-before-stale }}\n days with no activity. Remove the stale label or comment, or this will be\n closed in ${{ inputs.days-before-close }} days.\n";
|
|
@@ -3925,7 +4054,7 @@ var stale_default = "name: Stale\n\non: # yamllint disable-line rule:truthy\n w
|
|
|
3925
4054
|
var sync_github_default = "name: Sync GitHub Templates\n\n# Builds the holocron CLI from source and pushes updated workflow templates\n# and composite actions to downstream .github repos. Runs whenever the\n# template source files change on main or alpha.\n#\n# Secrets required:\n# HOLOCRON_SYNC_TOKEN — fine-grained PAT (resource owner: org) with:\n# Contents: Read and write (git trees, blobs, refs)\n# Pull requests: Read and write (open sync PR)\n# Workflows: Read and write (write .github/workflows/*.yml)\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n primary-repo:\n description: >\n Primary .github repo — receives composite actions, reusable workflows,\n and thin-caller templates. Requires a PR (branch protection assumed).\n type: string\n required: false\n default: theholocron/.github\n secondary-repos:\n description: >\n Space-separated list of secondary repos (reusable workflows + thin\n callers only, no composite actions). Changes are delivered via pull\n request, same as the primary repo.\n type: string\n required: false\n default: \"\"\n sync-branch:\n description: Branch name used for the primary and secondary repo PRs\n type: string\n required: false\n default: chore/sync-templates\n secrets:\n HOLOCRON_SYNC_TOKEN:\n required: true\n HOLOCRON_READ_TOKEN:\n description: >\n Fine-grained PAT for read-only GitHub API calls (e.g. resolving git\n committer identity via `gh api user`). Falls back to HOLOCRON_SYNC_TOKEN.\n required: false\n GH_TOKEN:\n description: >\n Generic GitHub token fallback for `gh` CLI calls. Used when neither\n HOLOCRON_READ_TOKEN nor HOLOCRON_SYNC_TOKEN is set.\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n sync:\n name: Sync templates\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm build\n name: Build CLI\n\n - name: Cache actionlint\n id: cache-actionlint\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: /tmp/actionlint\n key: actionlint-v1.7.7-linux-amd64\n\n - name: Download actionlint\n if: steps.cache-actionlint.outputs.cache-hit != 'true'\n run: |\n curl -fsSL https://github.com/rhysd/actionlint/releases/download/v1.7.7/actionlint_1.7.7_linux_amd64.tar.gz \\\n | tar -xz -C /tmp actionlint\n\n - name: Validate generated workflows\n run: |\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --output-dir /tmp/sync-validate\n /tmp/actionlint /tmp/sync-validate/.github/workflows/*.yml\n env:\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n\n - name: Sync primary repo (PR)\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$PRIMARY_REPO\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$PRIMARY_REPO\" \"$SYNC_BRANCH\" 2>/dev/null || true\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n PRIMARY_REPO: ${{ inputs.primary-repo }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n\n - name: Sync secondary repos (PR)\n if: ${{ inputs.secondary-repos != '' }}\n run: |\n GIT_NAME=$(gh api user --jq .name 2>/dev/null || echo \"github-actions[bot]\")\n GIT_EMAIL=$(gh api user --jq '\"\\(.id)+\\(.login)@users.noreply.github.com\"' 2>/dev/null || echo \"41898282+github-actions[bot]@users.noreply.github.com\")\n COMMIT_MSG=\"chore: sync from theholocron/holocron\"$'\\n\\n'\"Signed-off-by: $GIT_NAME <$GIT_EMAIL>\"\n for repo in $SECONDARY_REPOS; do\n node packages/cli/dist/cli.mjs sync-github \\\n --repo \"$repo\" \\\n --branch \"$SYNC_BRANCH\" \\\n --pr \\\n --message \"$COMMIT_MSG\"\n GH_TOKEN=\"$HOLOCRON_SYNC_TOKEN\" gh pr merge --auto --squash \\\n --repo \"$repo\" \"$SYNC_BRANCH\" 2>/dev/null || true\n done\n env:\n GH_TOKEN: ${{ secrets.HOLOCRON_READ_TOKEN || secrets.HOLOCRON_SYNC_TOKEN || secrets.GH_TOKEN }}\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n SECONDARY_REPOS: ${{ inputs.secondary-repos }}\n SYNC_BRANCH: ${{ inputs.sync-branch }}\n";
|
|
3926
4055
|
//#endregion
|
|
3927
4056
|
//#region src/templates/workflows/test.yml
|
|
3928
|
-
var test_default = "name: Test\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n TURBO_TOKEN:\n required: false\n\njobs:\n unit:\n name: Run tests and collect coverage\n permissions:\n contents: read\n id-token: write\n runs-on: ubuntu-latest\n timeout-minutes: 15\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm test
|
|
4057
|
+
var test_default = "name: Test\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n TURBO_TOKEN:\n required: false\n\njobs:\n unit:\n name: Run tests and collect coverage\n permissions:\n contents: read\n id-token: write\n runs-on: ubuntu-latest\n timeout-minutes: 15\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm test:coverage\n name: Run tests with coverage\n\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload coverage to Codecov\n with:\n use_oidc: true\n\n - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1\n name: Upload test results to Codecov\n if: ${{ !cancelled() }}\n with:\n use_oidc: true\n files: '**/test-report.junit.xml'\n";
|
|
3929
4058
|
//#endregion
|
|
3930
4059
|
//#region src/templates/workflows/typecheck.yml
|
|
3931
4060
|
var typecheck_default = "name: Typecheck\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n TURBO_TOKEN:\n required: false\n\njobs:\n typecheck:\n name: tsc --noEmit\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 10\n env:\n TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}\n TURBO_TEAM: ${{ vars.TURBO_TEAM }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n\n - run: pnpm typecheck\n name: Type check\n";
|
|
@@ -5058,6 +5187,24 @@ try {
|
|
|
5058
5187
|
}).option("description", {
|
|
5059
5188
|
type: "string",
|
|
5060
5189
|
describe: "Short description — replaces <description> placeholders in the template"
|
|
5190
|
+
}).option("homepage", {
|
|
5191
|
+
type: "string",
|
|
5192
|
+
describe: "Homepage URL — replaces <homepage> placeholders and appears in holocron.config.ts"
|
|
5193
|
+
}).option("vault", {
|
|
5194
|
+
type: "string",
|
|
5195
|
+
describe: "Vault provider: none, doppler, 1password, infisical"
|
|
5196
|
+
}).option("deployment", {
|
|
5197
|
+
type: "string",
|
|
5198
|
+
describe: "Deployment provider: none, vercel"
|
|
5199
|
+
}).option("agent", {
|
|
5200
|
+
type: "string",
|
|
5201
|
+
describe: "AI agent: claude, none"
|
|
5202
|
+
}).option("runtime-environment", {
|
|
5203
|
+
type: "string",
|
|
5204
|
+
describe: "Runtime environment: node, browser, universal, none"
|
|
5205
|
+
}).option("topics", {
|
|
5206
|
+
type: "string",
|
|
5207
|
+
describe: "Comma-separated repo topics (e.g. typescript,nodejs)"
|
|
5061
5208
|
}).option("org", {
|
|
5062
5209
|
type: "string",
|
|
5063
5210
|
default: "theholocron",
|
|
@@ -5065,29 +5212,135 @@ try {
|
|
|
5065
5212
|
}).option("verify", {
|
|
5066
5213
|
type: "boolean",
|
|
5067
5214
|
default: true,
|
|
5068
|
-
describe: "Run pnpm install after bootstrapping (
|
|
5215
|
+
describe: "Run pnpm install + holocron setup after bootstrapping (--no-verify skips)"
|
|
5069
5216
|
}), async (argv) => {
|
|
5070
5217
|
try {
|
|
5071
5218
|
let type = argv.type;
|
|
5072
5219
|
let name = argv.name;
|
|
5073
5220
|
let description = argv.description;
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5221
|
+
let homepage = argv.homepage;
|
|
5222
|
+
let vaultProvider = argv.vault;
|
|
5223
|
+
let vaultProject;
|
|
5224
|
+
let vaultConfig;
|
|
5225
|
+
let deploymentProvider = argv.deployment;
|
|
5226
|
+
let agent = argv.agent;
|
|
5227
|
+
let runtimeEnvironment = argv.runtimeEnvironment;
|
|
5228
|
+
let topics = parseTopics(argv.topics);
|
|
5229
|
+
if (!type) type = await select({
|
|
5230
|
+
message: "Template type:",
|
|
5231
|
+
choices: [
|
|
5232
|
+
{
|
|
5233
|
+
name: "node — Node.js library or tool",
|
|
5234
|
+
value: "node"
|
|
5235
|
+
},
|
|
5236
|
+
{
|
|
5237
|
+
name: "cli — CLI application (inquirer, chalk, yargs)",
|
|
5238
|
+
value: "cli"
|
|
5239
|
+
},
|
|
5240
|
+
{
|
|
5241
|
+
name: "monorepo — Turbo monorepo",
|
|
5242
|
+
value: "monorepo"
|
|
5243
|
+
},
|
|
5244
|
+
{
|
|
5245
|
+
name: "react — React component library",
|
|
5246
|
+
value: "react"
|
|
5247
|
+
},
|
|
5248
|
+
{
|
|
5249
|
+
name: "nextjs — Next.js application",
|
|
5250
|
+
value: "nextjs"
|
|
5251
|
+
},
|
|
5252
|
+
{
|
|
5253
|
+
name: "base — Minimal repo (no package.json)",
|
|
5254
|
+
value: "base"
|
|
5084
5255
|
}
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5256
|
+
]
|
|
5257
|
+
});
|
|
5258
|
+
if (!name) {
|
|
5259
|
+
name = await input({
|
|
5260
|
+
message: "Repo name (kebab-case):",
|
|
5261
|
+
validate: validateRepoName
|
|
5262
|
+
});
|
|
5263
|
+
name = name.trim();
|
|
5090
5264
|
}
|
|
5265
|
+
if (description === void 0) description = await input({ message: "Short description:" });
|
|
5266
|
+
if (homepage === void 0) homepage = (await input({ message: "Homepage URL (optional, Enter to skip):" })).trim() || void 0;
|
|
5267
|
+
if (!runtimeEnvironment) runtimeEnvironment = await select({
|
|
5268
|
+
message: "Runtime environment:",
|
|
5269
|
+
choices: [
|
|
5270
|
+
{
|
|
5271
|
+
name: "node — Node.js process",
|
|
5272
|
+
value: "node"
|
|
5273
|
+
},
|
|
5274
|
+
{
|
|
5275
|
+
name: "browser — Browser only",
|
|
5276
|
+
value: "browser"
|
|
5277
|
+
},
|
|
5278
|
+
{
|
|
5279
|
+
name: "universal — Node.js + browser",
|
|
5280
|
+
value: "universal"
|
|
5281
|
+
},
|
|
5282
|
+
{
|
|
5283
|
+
name: "none — No runtime (docs, config, etc.)",
|
|
5284
|
+
value: "none"
|
|
5285
|
+
}
|
|
5286
|
+
],
|
|
5287
|
+
default: type === "base" ? "none" : "node"
|
|
5288
|
+
});
|
|
5289
|
+
if (!vaultProvider) vaultProvider = await select({
|
|
5290
|
+
message: "Vault provider:",
|
|
5291
|
+
choices: [
|
|
5292
|
+
{
|
|
5293
|
+
name: "None",
|
|
5294
|
+
value: "none"
|
|
5295
|
+
},
|
|
5296
|
+
{
|
|
5297
|
+
name: "Doppler",
|
|
5298
|
+
value: "doppler"
|
|
5299
|
+
},
|
|
5300
|
+
{
|
|
5301
|
+
name: "1Password",
|
|
5302
|
+
value: "1password"
|
|
5303
|
+
},
|
|
5304
|
+
{
|
|
5305
|
+
name: "Infisical",
|
|
5306
|
+
value: "infisical"
|
|
5307
|
+
}
|
|
5308
|
+
]
|
|
5309
|
+
});
|
|
5310
|
+
if (vaultProvider === "doppler") {
|
|
5311
|
+
vaultProject = await input({
|
|
5312
|
+
message: "Doppler project name:",
|
|
5313
|
+
default: name
|
|
5314
|
+
});
|
|
5315
|
+
vaultConfig = await input({
|
|
5316
|
+
message: "Doppler config:",
|
|
5317
|
+
default: "dev"
|
|
5318
|
+
});
|
|
5319
|
+
} else if (vaultProvider === "1password" || vaultProvider === "infisical") vaultProject = await input({
|
|
5320
|
+
message: `${vaultProvider === "1password" ? "1Password vault" : "Infisical project"} name:`,
|
|
5321
|
+
default: name
|
|
5322
|
+
});
|
|
5323
|
+
if (!deploymentProvider) deploymentProvider = await select({
|
|
5324
|
+
message: "Deployment provider:",
|
|
5325
|
+
choices: [{
|
|
5326
|
+
name: "None",
|
|
5327
|
+
value: "none"
|
|
5328
|
+
}, {
|
|
5329
|
+
name: "Vercel",
|
|
5330
|
+
value: "vercel"
|
|
5331
|
+
}]
|
|
5332
|
+
});
|
|
5333
|
+
if (!agent) agent = await select({
|
|
5334
|
+
message: "AI agent:",
|
|
5335
|
+
choices: [{
|
|
5336
|
+
name: "Claude",
|
|
5337
|
+
value: "claude"
|
|
5338
|
+
}, {
|
|
5339
|
+
name: "None",
|
|
5340
|
+
value: "none"
|
|
5341
|
+
}]
|
|
5342
|
+
});
|
|
5343
|
+
if (topics.length === 0) topics = parseTopics(await input({ message: "Topics (comma-separated, optional):" }));
|
|
5091
5344
|
if (!type) {
|
|
5092
5345
|
console.error("new: template type is required");
|
|
5093
5346
|
process.exitCode = 1;
|
|
@@ -5101,7 +5354,15 @@ try {
|
|
|
5101
5354
|
if ((await runNew({
|
|
5102
5355
|
type,
|
|
5103
5356
|
name,
|
|
5104
|
-
|
|
5357
|
+
description: description || void 0,
|
|
5358
|
+
homepage,
|
|
5359
|
+
vaultProvider: vaultProvider ?? "none",
|
|
5360
|
+
vaultProject,
|
|
5361
|
+
vaultConfig,
|
|
5362
|
+
deploymentProvider: deploymentProvider ?? "none",
|
|
5363
|
+
agent: agent ?? "claude",
|
|
5364
|
+
runtimeEnvironment: runtimeEnvironment ?? "node",
|
|
5365
|
+
topics,
|
|
5105
5366
|
org: argv.org,
|
|
5106
5367
|
dryRun: argv.dryRun,
|
|
5107
5368
|
noVerify: !argv.verify,
|
|
@@ -5141,35 +5402,25 @@ try {
|
|
|
5141
5402
|
describe: "Run post-scaffold pnpm install + typecheck + lint + test (default true; --no-verify skips)"
|
|
5142
5403
|
}), async (argv) => {
|
|
5143
5404
|
try {
|
|
5144
|
-
const
|
|
5145
|
-
const
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
vendorEnv = argv.vendorEnv ? argv.vendorEnv : await ask(`Vendor-native env var for the ${argv.vendor} token (e.g. MYVENDOR_API_KEY):`);
|
|
5161
|
-
baseUrl = argv.baseUrl ? argv.baseUrl : await ask(`REST base URL for the ${argv.vendor} API (e.g. https://api.myvendor.com):`);
|
|
5162
|
-
} finally {
|
|
5163
|
-
rl.close();
|
|
5164
|
-
}
|
|
5165
|
-
} else {
|
|
5166
|
-
capability = argv.capability;
|
|
5167
|
-
vendorEnv = argv.vendorEnv;
|
|
5168
|
-
baseUrl = argv.baseUrl;
|
|
5169
|
-
}
|
|
5405
|
+
const vendor = argv.vendor;
|
|
5406
|
+
const { capability, vendorEnv, baseUrl } = await resolvePluginCreateInputs({
|
|
5407
|
+
capability: argv.capability,
|
|
5408
|
+
vendorEnv: argv.vendorEnv,
|
|
5409
|
+
baseUrl: argv.baseUrl
|
|
5410
|
+
}, {
|
|
5411
|
+
selectCapability: () => select({
|
|
5412
|
+
message: "Capability:",
|
|
5413
|
+
choices: Object.keys(CARDINALITY).map((k) => ({
|
|
5414
|
+
name: k,
|
|
5415
|
+
value: k
|
|
5416
|
+
}))
|
|
5417
|
+
}),
|
|
5418
|
+
inputVendorEnv: () => input({ message: `Vendor-native env var for the ${vendor} token (e.g. MYVENDOR_API_KEY):` }),
|
|
5419
|
+
inputBaseUrl: () => input({ message: `REST base URL for the ${vendor} API (e.g. https://api.myvendor.com):` })
|
|
5420
|
+
});
|
|
5170
5421
|
if (runPluginCreate({
|
|
5171
5422
|
slug: argv.slug,
|
|
5172
|
-
vendorName:
|
|
5423
|
+
vendorName: vendor,
|
|
5173
5424
|
capability,
|
|
5174
5425
|
vendorEnv,
|
|
5175
5426
|
baseUrl,
|