@theholocron/cli 3.17.2 → 3.18.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/dist/cli.mjs CHANGED
@@ -970,7 +970,10 @@ function patchFiles(dir, variants, description, homepage, runtimeEnvironment, pr
970
970
  if (isBinary(content)) continue;
971
971
  const original = content;
972
972
  for (const [search, replacement] of variants) content = content.split(search).join(replacement);
973
- if (description !== void 0) content = content.split("<description>").join(description);
973
+ if (description !== void 0) {
974
+ content = content.split("<description>").join(description);
975
+ content = content.replace(/(<!-- holocron:description -->)[\s\S]*?(<!-- \/holocron:description -->)/g, `$1\n${description}\n$2`);
976
+ }
974
977
  if (homepage !== void 0) content = content.split("<homepage>").join(homepage);
975
978
  if (runtimeEnvironment !== void 0) content = content.split("<runtime_environment>").join(runtimeEnvironment);
976
979
  content = content.replace(/\n?<!-- holocron:template-only -->[\s\S]*?<!-- \/holocron:template-only -->\n?/g, "");
@@ -989,9 +992,24 @@ function preflight$1() {
989
992
  function defaultExec$3(cmd, args, opts) {
990
993
  execFileSync(cmd, args, {
991
994
  cwd: opts.cwd,
992
- stdio: opts.stdio
995
+ stdio: opts.stdio,
996
+ ...opts.env && Object.keys(opts.env).length > 0 ? { env: {
997
+ ...process.env,
998
+ ...opts.env
999
+ } } : {}
993
1000
  });
994
1001
  }
1002
+ function keychainLookup(key) {
1003
+ const result = spawnSync("security", [
1004
+ "find-generic-password",
1005
+ "-s",
1006
+ "com.theholocron.cli",
1007
+ "-a",
1008
+ key,
1009
+ "-w"
1010
+ ], { encoding: "utf8" });
1011
+ return result.status === 0 ? result.stdout?.trim() || void 0 : void 0;
1012
+ }
995
1013
  function defaultReadFile(filepath) {
996
1014
  return readFileSync(filepath, "utf-8");
997
1015
  }
@@ -1023,13 +1041,14 @@ async function runNew(input) {
1023
1041
  }
1024
1042
  if (existsSync(repoDir)) throw new NewError(`\`${repoDir}\` already exists — delete it or pick a different name.`);
1025
1043
  print(` Creating ${newRepo} from template ${templateRepo}…`);
1044
+ const visibility = input.openSource ? "--public" : "--private";
1026
1045
  try {
1027
1046
  execFn("gh", [
1028
1047
  "repo",
1029
1048
  "create",
1030
1049
  newRepo,
1031
1050
  `--template=${templateRepo}`,
1032
- "--private",
1051
+ visibility,
1033
1052
  "--clone"
1034
1053
  ], {
1035
1054
  cwd,
@@ -1038,6 +1057,28 @@ async function runNew(input) {
1038
1057
  } catch (err) {
1039
1058
  throw new NewError(`gh repo create failed: ${err instanceof Error ? err.message : String(err)}`);
1040
1059
  }
1060
+ if (input.isTemplate) try {
1061
+ execFn("gh", [
1062
+ "repo",
1063
+ "edit",
1064
+ newRepo,
1065
+ "--template=true"
1066
+ ], {
1067
+ cwd,
1068
+ stdio: "inherit"
1069
+ });
1070
+ } catch {}
1071
+ try {
1072
+ execFn("gh", [
1073
+ "repo",
1074
+ "edit",
1075
+ newRepo,
1076
+ `--visibility=${input.openSource ? "public" : "private"}`
1077
+ ], {
1078
+ cwd,
1079
+ stdio: "inherit"
1080
+ });
1081
+ } catch {}
1041
1082
  let templateSlug = `${input.type}-template`;
1042
1083
  const pkgJsonPath = path.join(repoDir, "package.json");
1043
1084
  if (existsSync(pkgJsonPath)) try {
@@ -1048,6 +1089,8 @@ async function runNew(input) {
1048
1089
  print(` Patching files…`);
1049
1090
  const variants = deriveVariants(templateSlug, input.name);
1050
1091
  variants.unshift([`theholocron/${templateSlug}`, `${org}/${input.name}`]);
1092
+ const displayName = input.name.split("-").map(cap).join(" ");
1093
+ variants.push(["<display_name>", displayName]);
1051
1094
  const filesPatched = patchFiles(repoDir, variants, input.description, input.homepage, input.runtimeEnvironment, print, readFn, writeFn, walkFn);
1052
1095
  print(` ${filesPatched.length} file${filesPatched.length === 1 ? "" : "s"} patched`);
1053
1096
  if (input.description !== void 0 || input.homepage !== void 0) try {
@@ -1110,9 +1153,22 @@ async function runNew(input) {
1110
1153
  print("");
1111
1154
  print(" Running holocron setup…");
1112
1155
  try {
1113
- execFn("holocron", ["setup"], {
1156
+ const setupArgs = ["setup"];
1157
+ const setupEnv = {};
1158
+ const adminToken = input.token ?? keychainLookup("github.admin");
1159
+ if (adminToken) setupArgs.push("--token", adminToken);
1160
+ if (!process.env["HOLOCRON_ORG_TOKEN"]) {
1161
+ const orgToken = keychainLookup("github.org");
1162
+ if (orgToken) setupEnv["HOLOCRON_ORG_TOKEN"] = orgToken;
1163
+ }
1164
+ if (!process.env["HOLOCRON_DEPLOY_TOKEN"]) {
1165
+ const deployToken = keychainLookup("github.deploy");
1166
+ if (deployToken) setupEnv["HOLOCRON_DEPLOY_TOKEN"] = deployToken;
1167
+ }
1168
+ execFn("holocron", setupArgs, {
1114
1169
  cwd: repoDir,
1115
- stdio: "inherit"
1170
+ stdio: "inherit",
1171
+ env: setupEnv
1116
1172
  });
1117
1173
  } catch {
1118
1174
  print(" ✗ holocron setup failed — run it manually after checking your config");
@@ -3144,6 +3200,10 @@ async function runSetup(input) {
3144
3200
  if (loader.has("source")) {
3145
3201
  const source = loader.get("source");
3146
3202
  print(style.step("source"));
3203
+ const SECURITY_SKIP_CODES = {
3204
+ enableSecretScanning: [422],
3205
+ enablePrivateVulnerabilityReporting: [404]
3206
+ };
3147
3207
  for (const method of [
3148
3208
  "enableVulnerabilityAlerts",
3149
3209
  "enableAutomatedSecurityFixes",
@@ -3153,7 +3213,7 @@ async function runSetup(input) {
3153
3213
  ]) {
3154
3214
  steps.push(await runStep("source", method, dryRun, async () => {
3155
3215
  await source[method]();
3156
- }));
3216
+ }, { skipCodes: SECURITY_SKIP_CODES[method] }));
3157
3217
  print(formatStep(steps[steps.length - 1]));
3158
3218
  }
3159
3219
  const usesAdvancedCodeQL = (config.workflows ?? []).map((e) => typeof e === "string" ? e : e.name).includes("codeql");
@@ -3287,7 +3347,7 @@ async function runSetup(input) {
3287
3347
  }
3288
3348
  const teams = repo?.teams ?? [];
3289
3349
  if (teams.length > 0) if (source.syncTeams) {
3290
- steps.push(await runStep("source", "sync teams", dryRun, () => source.syncTeams(teams)));
3350
+ steps.push(await runStep("source", "sync teams", dryRun, () => source.syncTeams(teams), { skipCodes: [422] }));
3291
3351
  print(formatStep(steps[steps.length - 1]));
3292
3352
  const repoCoord = input.context.repo ?? repo?.name ?? "";
3293
3353
  const org = repoCoord.includes("/") ? repoCoord.split("/")[0] : "";
@@ -3654,7 +3714,7 @@ async function updateSkillsGitignore(gitignorePath, existingContent, skills, sym
3654
3714
  } else content = (existingContent.trimEnd() ? existingContent.trimEnd() + "\n\n" : "") + block + "\n";
3655
3715
  await writeFile(gitignorePath, content, "utf8");
3656
3716
  }
3657
- async function runStep(capability, step, dryRun, body) {
3717
+ async function runStep(capability, step, dryRun, body, opts = {}) {
3658
3718
  if (dryRun) return {
3659
3719
  capability,
3660
3720
  step,
@@ -3670,15 +3730,23 @@ async function runStep(capability, step, dryRun, body) {
3670
3730
  if (typeof note === "string") result.message = note;
3671
3731
  return result;
3672
3732
  } catch (err) {
3673
- if (err instanceof ProviderApiError$1 && err.status === 403) {
3674
- const reason = classify403(err);
3675
- return {
3733
+ if (err instanceof ProviderApiError$1) {
3734
+ if (err.status !== void 0 && opts.skipCodes?.includes(err.status)) return {
3676
3735
  capability,
3677
3736
  step,
3678
- status: "fail",
3679
- message: err.message,
3680
- reason
3737
+ status: "skip",
3738
+ message: err.message
3681
3739
  };
3740
+ if (err.status === 403) {
3741
+ const reason = classify403(err);
3742
+ return {
3743
+ capability,
3744
+ step,
3745
+ status: reason === "plan" ? "skip" : "fail",
3746
+ message: err.message,
3747
+ reason
3748
+ };
3749
+ }
3682
3750
  }
3683
3751
  return {
3684
3752
  capability,
@@ -4105,7 +4173,7 @@ var codeql_default = "name: CodeQL\n\non: # yamllint disable-line rule:truthy\n
4105
4173
  var dependencies_default = "name: Dependencies\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n merge-token:\n description: >\n Optional privileged token for auto-merge. Falls back to GITHUB_TOKEN.\n Required when branch protection enforces required reviews — GITHUB_TOKEN\n cannot approve its own PRs.\n required: false\n\njobs:\n dependabot:\n name: Update the dependencies\n permissions:\n contents: write\n pull-requests: write\n runs-on: ubuntu-latest\n timeout-minutes: 5\n if: github.event.pull_request.user.login == 'dependabot[bot]'\n steps:\n - uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0\n name: Fetch Dependabot metadata\n id: metadata\n\n - run: gh pr merge --auto --squash \"$PR_URL\"\n # --squash is intentional: repo protection sets allow_merge_commit: false,\n # so --merge would fail on any repo using the standard preset.\n name: Enable auto-merge for Dependabot PRs\n if: steps.metadata.outputs.update-type == 'version-update:semver-patch'\n env:\n PR_URL: ${{ github.event.pull_request.html_url }}\n GH_TOKEN: ${{ secrets.merge-token || github.token }}\n";
4106
4174
  //#endregion
4107
4175
  //#region src/templates/workflows/deploy-docs.yml
4108
- var deploy_docs_default = "name: Deploy Docs\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n name:\n description: Repo name prefix used to derive package names (<name>-docs, <name>-site)\n required: true\n type: string\n use-turbo:\n description: Build content packages with pnpm turbo build (default true)\n required: false\n type: boolean\n default: true\n\njobs:\n build:\n name: Build\n runs-on: ubuntu-latest\n permissions:\n contents: read\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 - name: Build content packages\n if: ${{ inputs.use-turbo }}\n env:\n DOCS_NAME: ${{ inputs.name }}\n run: pnpm turbo build --filter=@theholocron/\"$DOCS_NAME\"-docs\n\n - name: Build content packages\n if: ${{ !inputs.use-turbo }}\n env:\n DOCS_NAME: ${{ inputs.name }}\n run: pnpm --filter @theholocron/\"$DOCS_NAME\"-docs build\n\n - name: Build docs site\n env:\n DOCS_NAME: ${{ inputs.name }}\n run: pnpm --filter @theholocron/\"$DOCS_NAME\"-site build\n\n - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1\n name: Upload pages artifact\n with:\n path: docs/dist\n\n deploy:\n name: Deploy\n needs: build\n runs-on: ubuntu-latest\n permissions:\n pages: write\n id-token: write\n environment:\n name: github-pages\n url: ${{ steps.deployment.outputs.page_url }}\n steps:\n - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5\n id: deployment\n name: Deploy to GitHub Pages\n";
4176
+ var deploy_docs_default = "name: Deploy Docs\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n name:\n description: Repo name prefix used to derive package names (<name>-docs, <name>-site)\n required: true\n type: string\n use-turbo:\n description: Build content packages with pnpm turbo build (default true)\n required: false\n type: boolean\n default: true\n skip-content:\n description: Skip the content package build step (for repos with no <name>-docs package)\n required: false\n type: boolean\n default: false\n\njobs:\n build:\n name: Build\n runs-on: ubuntu-latest\n permissions:\n contents: read\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 - name: Build content packages\n if: ${{ !inputs.skip-content && inputs.use-turbo }}\n env:\n DOCS_NAME: ${{ inputs.name }}\n run: pnpm turbo build --filter=@theholocron/\"$DOCS_NAME\"-docs\n\n - name: Build content packages\n if: ${{ !inputs.skip-content && !inputs.use-turbo }}\n env:\n DOCS_NAME: ${{ inputs.name }}\n run: pnpm --filter @theholocron/\"$DOCS_NAME\"-docs build\n\n - name: Build docs site\n env:\n DOCS_NAME: ${{ inputs.name }}\n run: pnpm --filter @theholocron/\"$DOCS_NAME\"-site build\n\n - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1\n name: Upload pages artifact\n with:\n path: docs/dist\n\n deploy:\n name: Deploy\n needs: build\n runs-on: ubuntu-latest\n permissions:\n pages: write\n id-token: write\n environment:\n name: github-pages\n url: ${{ steps.deployment.outputs.page_url }}\n steps:\n - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5\n id: deployment\n name: Deploy to GitHub Pages\n";
4109
4177
  //#endregion
4110
4178
  //#region src/templates/workflows/deploy-storybook.yml
4111
4179
  var deploy_storybook_default = "name: Deploy Storybook\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n build-script:\n description: pnpm script that builds the Storybook static output\n type: string\n required: false\n default: build:storybook\n output-dir:\n description: Directory where Storybook writes its static output\n type: string\n required: false\n default: storybook-static\n\njobs:\n build:\n name: Build\n runs-on: ubuntu-latest\n permissions:\n contents: read\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 - name: Build Storybook\n run: pnpm run \"$BUILD_SCRIPT\"\n env:\n BUILD_SCRIPT: ${{ inputs.build-script }}\n\n - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1\n name: Upload pages artifact\n with:\n path: ${{ inputs.output-dir }}\n\n deploy:\n name: Deploy\n needs: build\n runs-on: ubuntu-latest\n permissions:\n pages: write\n id-token: write\n environment:\n name: github-pages\n url: ${{ steps.deployment.outputs.page_url }}\n steps:\n - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5\n id: deployment\n name: Deploy to GitHub Pages\n";
@@ -5385,6 +5453,9 @@ try {
5385
5453
  }).option("skills", {
5386
5454
  type: "string",
5387
5455
  describe: "Comma-separated agent skill names (e.g. git-safety,pr-workflow)"
5456
+ }).option("is-template", {
5457
+ type: "boolean",
5458
+ describe: "Mark the new repo as a GitHub template repository"
5388
5459
  }).option("org", {
5389
5460
  type: "string",
5390
5461
  default: "theholocron",
@@ -5395,6 +5466,9 @@ try {
5395
5466
  describe: "Run pnpm install + holocron setup after bootstrapping (--no-verify skips)"
5396
5467
  }), async (argv) => {
5397
5468
  try {
5469
+ const tokens = tokenContext(argv.token);
5470
+ if (!tokens) return;
5471
+ const adminToken = tokens.cliTokens?.["github"] ?? tokens.cliToken;
5398
5472
  let type = argv.type;
5399
5473
  let name = argv.name;
5400
5474
  let description = argv.description;
@@ -5410,6 +5484,7 @@ try {
5410
5484
  let openSource = argv.openSource;
5411
5485
  let usesExternalPackages = argv.usesExternalPackages;
5412
5486
  let skills = parseTopics(argv.skills);
5487
+ const isTemplate = argv.isTemplate;
5413
5488
  if (!type) type = await select({
5414
5489
  message: "Template type:",
5415
5490
  choices: [
@@ -5589,7 +5664,9 @@ try {
5589
5664
  usesExternalPackages: usesExternalPackages ?? true,
5590
5665
  topics,
5591
5666
  skills,
5667
+ isTemplate,
5592
5668
  org: argv.org,
5669
+ token: adminToken,
5593
5670
  dryRun: argv.dryRun,
5594
5671
  noVerify: !argv.verify,
5595
5672
  cwd: argv.cwd