@theholocron/cli 3.6.0 → 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/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 { stdin, stdout } from "node:process";
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";
@@ -817,19 +816,21 @@ function pad(s, width) {
817
816
  /**
818
817
  * `holocron new <type> <name>` — create a GitHub repo from a template and
819
818
  * bootstrap it by replacing all template-slug casing variants with the new
820
- * project name.
819
+ * project name, then generate a `holocron.config.ts` from the answers given
820
+ * during the interactive wizard.
821
821
  *
822
822
  * Flow:
823
823
  * 1. Preflight — verify `gh` CLI is available.
824
- * 2. Resolve type, name, description (prompt via readline if missing).
824
+ * 2. Resolve type, name, description, homepage, vault/deployment/agent options.
825
825
  * 3. `gh repo create <org>/<name> --template <org>/<type>-template --private --clone`
826
826
  * → clones to `<cwd>/<name>/`
827
827
  * 4. Detect template slug from cloned package.json.
828
828
  * 5. Replace all casing variants of the slug across every text file.
829
- * 6. Replace `<description>` placeholder if a description was given.
830
- * 7. Commit the patched files (-s for DCO).
831
- * 8. Unless --no-verify: `pnpm install` in the new repo.
832
- * 9. Print next steps.
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.
833
834
  */
834
835
  var NewError = class extends Error {
835
836
  name = "NewError";
@@ -863,6 +864,63 @@ function deriveVariants(slug, name) {
863
864
  return true;
864
865
  });
865
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
+ }
866
924
  const SKIP_DIRS$1 = /* @__PURE__ */ new Set([
867
925
  ".git",
868
926
  "node_modules",
@@ -884,7 +942,7 @@ function isBinary(content) {
884
942
  for (let i = 0; i < Math.min(content.length, 8e3); i++) if (content.charCodeAt(i) === 0) return true;
885
943
  return false;
886
944
  }
887
- function patchFiles(dir, variants, description, print, readFn, writeFn, walkFn) {
945
+ function patchFiles(dir, variants, description, homepage, runtimeEnvironment, print, readFn, writeFn, walkFn) {
888
946
  const patched = [];
889
947
  for (const filepath of walkFn(dir)) {
890
948
  let content;
@@ -897,6 +955,8 @@ function patchFiles(dir, variants, description, print, readFn, writeFn, walkFn)
897
955
  const original = content;
898
956
  for (const [search, replacement] of variants) content = content.split(search).join(replacement);
899
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);
900
960
  if (content !== original) {
901
961
  writeFn(filepath, content);
902
962
  print(` ✓ ${path.relative(dir, filepath)}`);
@@ -939,6 +999,9 @@ async function runNew(input) {
939
999
  print(` Would clone to ${repoDir}`);
940
1000
  print(` Would patch all casing variants of "${input.type}-template" → "${input.name}"`);
941
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`);
942
1005
  return { status: "dry-run" };
943
1006
  }
944
1007
  if (existsSync(repoDir)) throw new NewError(`\`${repoDir}\` already exists — delete it or pick a different name.`);
@@ -962,27 +1025,40 @@ async function runNew(input) {
962
1025
  const pkgJsonPath = path.join(repoDir, "package.json");
963
1026
  if (existsSync(pkgJsonPath)) try {
964
1027
  const pkg = JSON.parse(readFn(pkgJsonPath));
965
- if (typeof pkg.name === "string") templateSlug = pkg.name.split("/").pop() ?? templateSlug;
1028
+ if (typeof pkg.name === "string") templateSlug = pkg.name.split("/").at(-1) || templateSlug;
966
1029
  } catch {}
967
1030
  print(` Detected template slug: ${templateSlug}`);
968
1031
  print(` Patching files…`);
969
- 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);
970
1033
  print(` ${filesPatched.length} file${filesPatched.length === 1 ? "" : "s"} patched`);
971
- if (filesPatched.length > 0) {
972
- execFn("git", ["add", "-A"], {
973
- cwd: repoDir,
974
- stdio: "inherit"
975
- });
976
- execFn("git", [
977
- "commit",
978
- "-s",
979
- "-m",
980
- `chore: bootstrap from ${templateSlug}`
981
- ], {
982
- cwd: repoDir,
983
- stdio: "inherit"
984
- });
985
- }
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
+ });
986
1062
  if (!input.noVerify) {
987
1063
  print("");
988
1064
  print(" Installing dependencies…");
@@ -1000,16 +1076,27 @@ async function runNew(input) {
1000
1076
  message: "pnpm install failed; inspect output above"
1001
1077
  };
1002
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
+ }
1003
1089
  }
1004
1090
  print("");
1005
1091
  print(` Scaffolded ${newRepo} (${filesPatched.length} file${filesPatched.length === 1 ? "" : "s"} patched).`);
1006
1092
  print("");
1007
1093
  print(" Next:");
1008
1094
  print(` 1. cd ${repoDir}`);
1009
- if (input.noVerify) print(` 2. pnpm install`);
1010
- const step = input.noVerify ? 3 : 2;
1011
- print(` ${step}. holocron setup # wire up secrets, teams, labels, etc.`);
1012
- print(` ${step + 1}. git push -u origin HEAD`);
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`);
1013
1100
  return {
1014
1101
  status: "ok",
1015
1102
  repoDir,
@@ -2077,6 +2164,18 @@ export default defineConfig({
2077
2164
  * pnpm --filter <pkg> typecheck lint test.
2078
2165
  * 6. Print next steps.
2079
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
+ }
2080
2179
  var PluginCreateError = class extends Error {
2081
2180
  name = "PluginCreateError";
2082
2181
  };
@@ -3946,7 +4045,7 @@ var lint_default = "name: Lint\n\non: # yamllint disable-line rule:truthy\n wor
3946
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";
3947
4046
  //#endregion
3948
4047
  //#region src/templates/workflows/review.yml
3949
- 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";
3950
4049
  //#endregion
3951
4050
  //#region src/templates/workflows/stale.yml
3952
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";
@@ -3955,7 +4054,7 @@ var stale_default = "name: Stale\n\non: # yamllint disable-line rule:truthy\n w
3955
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";
3956
4055
  //#endregion
3957
4056
  //#region src/templates/workflows/test.yml
3958
- 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";
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";
3959
4058
  //#endregion
3960
4059
  //#region src/templates/workflows/typecheck.yml
3961
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";
@@ -5088,6 +5187,24 @@ try {
5088
5187
  }).option("description", {
5089
5188
  type: "string",
5090
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)"
5091
5208
  }).option("org", {
5092
5209
  type: "string",
5093
5210
  default: "theholocron",
@@ -5095,29 +5212,135 @@ try {
5095
5212
  }).option("verify", {
5096
5213
  type: "boolean",
5097
5214
  default: true,
5098
- describe: "Run pnpm install after bootstrapping (default true; --no-verify skips)"
5215
+ describe: "Run pnpm install + holocron setup after bootstrapping (--no-verify skips)"
5099
5216
  }), async (argv) => {
5100
5217
  try {
5101
5218
  let type = argv.type;
5102
5219
  let name = argv.name;
5103
5220
  let description = argv.description;
5104
- if (!type || !name || description === void 0) {
5105
- const rl = createInterface({
5106
- input: stdin,
5107
- output: stdout
5108
- });
5109
- const ask = (question) => new Promise((resolve) => rl.question(` ${question} `, (answer) => resolve(answer.trim())));
5110
- try {
5111
- if (!type) {
5112
- console.log(" Known types: base, cli, monorepo, nextjs, node, react");
5113
- type = await ask("Template type:");
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"
5114
5255
  }
5115
- if (!name) name = await ask("Repo name (kebab-case):");
5116
- if (description === void 0) description = await ask("Short description (Enter to skip):");
5117
- } finally {
5118
- rl.close();
5119
- }
5256
+ ]
5257
+ });
5258
+ if (!name) {
5259
+ name = await input({
5260
+ message: "Repo name (kebab-case):",
5261
+ validate: validateRepoName
5262
+ });
5263
+ name = name.trim();
5120
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):" }));
5121
5344
  if (!type) {
5122
5345
  console.error("new: template type is required");
5123
5346
  process.exitCode = 1;
@@ -5131,7 +5354,15 @@ try {
5131
5354
  if ((await runNew({
5132
5355
  type,
5133
5356
  name,
5134
- ...description ? { description } : {},
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,
5135
5366
  org: argv.org,
5136
5367
  dryRun: argv.dryRun,
5137
5368
  noVerify: !argv.verify,
@@ -5171,35 +5402,25 @@ try {
5171
5402
  describe: "Run post-scaffold pnpm install + typecheck + lint + test (default true; --no-verify skips)"
5172
5403
  }), async (argv) => {
5173
5404
  try {
5174
- const capabilityKeys = Object.keys(CARDINALITY).join(", ");
5175
- const needsPrompt = !argv.capability || !argv.vendorEnv || !argv.baseUrl;
5176
- let capability;
5177
- let vendorEnv;
5178
- let baseUrl;
5179
- if (needsPrompt) {
5180
- const rl = createInterface({
5181
- input: stdin,
5182
- output: stdout
5183
- });
5184
- const ask = (question) => new Promise((resolve) => rl.question(` ${question} `, (answer) => resolve(answer.trim())));
5185
- try {
5186
- if (!argv.capability) {
5187
- console.log(` Available capabilities: ${capabilityKeys}`);
5188
- capability = await ask("Capability:");
5189
- } else capability = argv.capability;
5190
- vendorEnv = argv.vendorEnv ? argv.vendorEnv : await ask(`Vendor-native env var for the ${argv.vendor} token (e.g. MYVENDOR_API_KEY):`);
5191
- baseUrl = argv.baseUrl ? argv.baseUrl : await ask(`REST base URL for the ${argv.vendor} API (e.g. https://api.myvendor.com):`);
5192
- } finally {
5193
- rl.close();
5194
- }
5195
- } else {
5196
- capability = argv.capability;
5197
- vendorEnv = argv.vendorEnv;
5198
- baseUrl = argv.baseUrl;
5199
- }
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
+ });
5200
5421
  if (runPluginCreate({
5201
5422
  slug: argv.slug,
5202
- vendorName: argv.vendor,
5423
+ vendorName: vendor,
5203
5424
  capability,
5204
5425
  vendorEnv,
5205
5426
  baseUrl,