@theholocron/cli 2.2.3 → 3.0.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/README.md +30 -0
- package/dist/cli.mjs +96 -19
- package/dist/index.d.mts +23 -2
- package/dist/index.mjs +32 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -138,6 +138,36 @@ export default acmeConfig;
|
|
|
138
138
|
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
+
## Auth — fine-grained tokens
|
|
142
|
+
|
|
143
|
+
Each GitHub capability resolves its own fine-grained PAT so a compromised
|
|
144
|
+
credential only affects that feature. Store them once in the OS keyring
|
|
145
|
+
(macOS Keychain, Windows Credential Manager, libsecret on Linux):
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
holocron auth set github.read ghp_xxx # clone + CI run listing
|
|
149
|
+
holocron auth set github.issues ghp_xxx # issue management
|
|
150
|
+
holocron auth set github.sync ghp_xxx # sync-github workflow templates
|
|
151
|
+
holocron auth set github.release ghp_xxx # semantic-release
|
|
152
|
+
holocron auth set github.admin ghp_xxx # setup, secrets, environments
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The resolution chain per capability is:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
--token flag → HOLOCRON_<FEATURE>_TOKEN env var → keyring("github.<feature>")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
See [`docs/tokens.md`](../../docs/tokens.md) for required PAT scopes per feature.
|
|
162
|
+
|
|
163
|
+
Additional `auth` subcommands:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
holocron auth check github.read # re-verify a stored token
|
|
167
|
+
holocron auth unset github.read # remove a stored token
|
|
168
|
+
holocron auth list # show all stored providers
|
|
169
|
+
```
|
|
170
|
+
|
|
141
171
|
## What's in here
|
|
142
172
|
|
|
143
173
|
- `src/capabilities/` — the 14 capability interfaces that providers
|
package/dist/cli.mjs
CHANGED
|
@@ -296,20 +296,23 @@ async function runAuthSet(input) {
|
|
|
296
296
|
positional: input.positional,
|
|
297
297
|
env: input.env
|
|
298
298
|
});
|
|
299
|
-
const
|
|
299
|
+
const isFeatureKey = provider.includes(".");
|
|
300
|
+
const packageName = isFeatureKey ? null : resolvePluginPackage(provider);
|
|
300
301
|
if (!token) {
|
|
301
302
|
print(style.fail(`no token supplied for \`${provider}\`.`));
|
|
302
303
|
print(style.hint(` pass as positional arg: holocron auth set ${provider} <token>`));
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
if (!isFeatureKey) {
|
|
305
|
+
print(style.hint(` or via env: HOLOCRON_${provider.toUpperCase()}_TOKEN / ${provider.toUpperCase()}_TOKEN`));
|
|
306
|
+
const hint = await tryLoadHint(importer, packageName);
|
|
307
|
+
if (hint) print(style.hint(` hint: ${hint}`));
|
|
308
|
+
}
|
|
306
309
|
return {
|
|
307
310
|
status: "fail",
|
|
308
311
|
message: "no token supplied"
|
|
309
312
|
};
|
|
310
313
|
}
|
|
311
314
|
let subject;
|
|
312
|
-
try {
|
|
315
|
+
if (!isFeatureKey && packageName) try {
|
|
313
316
|
const module = await importer(packageName);
|
|
314
317
|
if (typeof module.verifyToken === "function") {
|
|
315
318
|
const verified = await module.verifyToken(token);
|
|
@@ -365,6 +368,13 @@ async function runAuthCheck(input) {
|
|
|
365
368
|
message: "no stored token"
|
|
366
369
|
};
|
|
367
370
|
}
|
|
371
|
+
if (provider.includes(".")) {
|
|
372
|
+
print(style.success(`${provider}: token stored (feature key — no plugin verification)`));
|
|
373
|
+
return {
|
|
374
|
+
status: "ok",
|
|
375
|
+
message: "stored"
|
|
376
|
+
};
|
|
377
|
+
}
|
|
368
378
|
const packageName = resolvePluginPackage(provider);
|
|
369
379
|
try {
|
|
370
380
|
const module = await importer(packageName);
|
|
@@ -434,6 +444,12 @@ async function tryLoadHint(importer, packageName) {
|
|
|
434
444
|
}
|
|
435
445
|
//#endregion
|
|
436
446
|
//#region src/commands/clone.ts
|
|
447
|
+
function encodeTokenForGitHttpAuth(token) {
|
|
448
|
+
const trimmed = token.trim();
|
|
449
|
+
if (!trimmed) throw new Error("empty token");
|
|
450
|
+
if (/[\u0000-\u001F\u007F\s]/.test(trimmed)) throw new Error("token contains whitespace or control characters");
|
|
451
|
+
return encodeURIComponent(trimmed);
|
|
452
|
+
}
|
|
437
453
|
async function listOrgRepos(org, token, fetchFn) {
|
|
438
454
|
const repos = [];
|
|
439
455
|
let url = `https://api.github.com/orgs/${org}/repos?per_page=100&type=all`;
|
|
@@ -492,9 +508,24 @@ async function runClone(input) {
|
|
|
492
508
|
continue;
|
|
493
509
|
}
|
|
494
510
|
print(style.step(` clone ${repo.full_name}`));
|
|
511
|
+
const { clone_url } = repo;
|
|
512
|
+
if (!clone_url.startsWith("https://github.com/")) {
|
|
513
|
+
print(style.fail(` failed ${repo.full_name} — unexpected clone URL: ${clone_url}`));
|
|
514
|
+
failed++;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
let encodedToken;
|
|
518
|
+
try {
|
|
519
|
+
encodedToken = encodeTokenForGitHttpAuth(input.token);
|
|
520
|
+
} catch (err) {
|
|
521
|
+
print(style.fail(` failed ${repo.full_name} — invalid token format: ${err instanceof Error ? err.message : String(err)}`));
|
|
522
|
+
failed++;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
495
525
|
if (exec("git", [
|
|
496
526
|
"clone",
|
|
497
|
-
|
|
527
|
+
"--",
|
|
528
|
+
`https://x-access-token:${encodedToken}@github.com/${clone_url.slice(19)}`,
|
|
498
529
|
dest
|
|
499
530
|
], { cwd: targetDir }).status !== 0) {
|
|
500
531
|
print(style.fail(` failed ${repo.full_name}`));
|
|
@@ -1199,16 +1230,16 @@ var greetings_default$1 = "name: Greetings\n\non: # yamllint disable-line rule:t
|
|
|
1199
1230
|
var lint_default$1 = "name: Lint\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n prettier-config:\n type: string\n required: false\n default: prettier.config.js\n yaml-config:\n type: string\n required: false\n default: yamllint.config.yml\n enable-auto-commit:\n description: Auto-commit super-linter fixes via GPG-signed commit\n type: boolean\n required: false\n default: false\n secrets:\n SUPER_LINTER_GPG_PRIVATE_KEY:\n required: false\n SUPER_LINTER_GPG_PASSPHRASE:\n required: false\n\njobs:\n super-lint:\n name: Lint entire codebase\n permissions:\n contents: write\n statuses: write\n runs-on: ubuntu-latest\n timeout-minutes: 30\n env:\n GPG_KEY_SET: ${{ secrets.SUPER_LINTER_GPG_PRIVATE_KEY != '' }}\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\n token: ${{ github.token }}\n\n - uses: theholocron/.github/.github/actions/setup@main\n name: Setup\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n\n - uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0\n name: Run Super Linter\n env:\n GITHUB_TOKEN: ${{ github.token }}\n DEFAULT_BRANCH: ${{ github.event.pull_request.base.ref || github.event.repository.default_branch }}\n ANNOTATE_ONLY: true\n DISABLE_COMMENTS: false\n IGNORE_GITIGNORED_FILES: true\n LINTER_RULES_PATH: /\n EDITORCONFIG_FILE_NAME: \".editorconfig-checker.json\"\n FIX_ENV: true\n FIX_GRAPHQL_PRETTIER: true\n FIX_HTML_PRETTIER: true\n FIX_JAVASCRIPT_PRETTIER: true\n FIX_JSX_PRETTIER: true\n FIX_MARKDOWN_PRETTIER: true\n FIX_TSX: true\n FIX_TYPESCRIPT_PRETTIER: true\n PRETTIER_CONFIG: ${{ inputs.prettier-config }}\n VALIDATE_DOCKERFILE: true\n VALIDATE_EDITORCONFIG: true\n VALIDATE_ENV: true\n VALIDATE_GIT_COMMITLINT: true\n VALIDATE_GIT_MERGE_CONFLICT_MARKERS: true\n VALIDATE_GITHUB_ACTIONS: true\n VALIDATE_GITLEAKS: true\n VALIDATE_GRAPHQL_PRETTIER: true\n VALIDATE_HTML_PRETTIER: true\n VALIDATE_JAVASCRIPT_PRETTIER: true\n VALIDATE_JSX_PRETTIER: true\n VALIDATE_MARKDOWN_PRETTIER: true\n VALIDATE_TSX: true\n VALIDATE_TYPESCRIPT_PRETTIER: true\n VALIDATE_YAML: true\n YAML_CONFIG_FILE: ${{ inputs.yaml-config }}\n\n - uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0\n name: Import GPG Key\n # Conditions mirror auto-commit exactly — no point importing GPG if the\n # commit step will be skipped (fork PR, default branch, or secret unset).\n if: >\n inputs.enable-auto-commit == true &&\n github.event.pull_request != null &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.ref_name != github.event.repository.default_branch &&\n env.GPG_KEY_SET == 'true'\n with:\n git_user_signingkey: true\n git_commit_gpgsign: true\n GPG_PRIVATE_KEY: ${{ secrets.SUPER_LINTER_GPG_PRIVATE_KEY }}\n PASSPHRASE: ${{ secrets.SUPER_LINTER_GPG_PASSPHRASE }}\n\n - uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0\n name: Commit and push linting fixes\n if: >\n inputs.enable-auto-commit == true &&\n github.event.pull_request != null &&\n github.event.pull_request.head.repo.full_name == github.repository &&\n github.ref_name != github.event.repository.default_branch &&\n env.GPG_KEY_SET == 'true'\n with:\n branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}\n commit_message: \"chore: fix linting issues\\n\\nSigned-off-by: super-linter <super-linter@super-linter.dev>\"\n commit_options: \"--no-verify\"\n commit_user_name: super-linter\n commit_user_email: super-linter@super-linter.dev\n";
|
|
1200
1231
|
//#endregion
|
|
1201
1232
|
//#region src/templates/workflows/release.yml
|
|
1202
|
-
var release_default$1 = "name: Release\n\n# Semantic-release with OIDC Trusted Publishing — no NPM_TOKEN required.\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 secrets:\n
|
|
1233
|
+
var release_default$1 = "name: Release\n\n# Semantic-release with OIDC Trusted Publishing — no NPM_TOKEN required.\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 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\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 # 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 # 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 - run: npm install -g npm@11 sigstore\n name: Upgrade npm for OIDC support\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 NPM_CONFIG_PROVENANCE: true\n";
|
|
1203
1234
|
//#endregion
|
|
1204
1235
|
//#region src/templates/workflows/review.yml
|
|
1205
|
-
var review_default$1 = "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
|
|
1236
|
+
var review_default$1 = "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";
|
|
1206
1237
|
//#endregion
|
|
1207
1238
|
//#region src/templates/workflows/stale.yml
|
|
1208
1239
|
var stale_default$1 = "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";
|
|
1209
1240
|
//#endregion
|
|
1210
1241
|
//#region src/templates/workflows/sync-github.yml
|
|
1211
|
-
var sync_github_default$1 = "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#
|
|
1242
|
+
var sync_github_default$1 = "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\njobs:\n sync:\n name: Sync templates\n runs-on: ubuntu-latest\n timeout-minutes: 15\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 - run: pnpm build\n name: Build CLI\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 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 /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 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 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";
|
|
1212
1243
|
//#endregion
|
|
1213
1244
|
//#region src/templates/workflows/test.yml
|
|
1214
1245
|
var test_default$1 = "name: Test\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n secrets:\n CODECOV_TOKEN:\n required: false\n\njobs:\n unit:\n name: Run tests and collect coverage\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n name: Checkout repository\n with:\n fetch-depth: 0\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 token: ${{ secrets.CODECOV_TOKEN }}\n\n - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1\n name: Upload test results to Codecov\n if: ${{ !cancelled() }}\n with:\n token: ${{ secrets.CODECOV_TOKEN }}\n files: '**/test-report.junit.xml'\n";
|
|
@@ -1288,7 +1319,7 @@ var review_default = "name: Review\n\non: # yamllint disable-line rule:truthy\n
|
|
|
1288
1319
|
var stale_default = "name: Stale\n\non: # yamllint disable-line rule:truthy\n schedule:\n - cron: \"30 1 * * *\"\n\npermissions:\n contents: write\n issues: write\n pull-requests: write\n\njobs:\n stale:\n uses: theholocron/.github/.github/workflows/stale.yml@main\n secrets: inherit\n";
|
|
1289
1320
|
//#endregion
|
|
1290
1321
|
//#region src/commands/workflows/sync-github.yml
|
|
1291
|
-
var sync_github_default = "name: Sync GitHub Templates\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n paths:\n - packages/cli/src/templates/index.ts\n - packages/cli/src/commands/setup-workflows.ts\n\nconcurrency:\n group: sync-github-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n\njobs:\n sync:\n name: Sync\n uses: theholocron/.github/.github/workflows/sync-github.yml@main\n with:\n secondary-repos: theholocron/.github-private\n secrets:\n
|
|
1322
|
+
var sync_github_default = "name: Sync GitHub Templates\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n paths:\n - packages/cli/src/templates/index.ts\n - packages/cli/src/commands/setup-workflows.ts\n\nconcurrency:\n group: sync-github-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n\njobs:\n sync:\n name: Sync\n uses: theholocron/.github/.github/workflows/sync-github.yml@main\n with:\n secondary-repos: theholocron/.github-private\n secrets:\n HOLOCRON_SYNC_TOKEN: ${{ secrets.HOLOCRON_SYNC_TOKEN }}\n";
|
|
1292
1323
|
//#endregion
|
|
1293
1324
|
//#region src/commands/workflows/test.yml
|
|
1294
1325
|
var test_default = "name: Test\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: test-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n\njobs:\n test:\n name: Test\n uses: theholocron/.github/.github/workflows/test.yml@main\n secrets: inherit\n";
|
|
@@ -4020,6 +4051,39 @@ function runSkillsUpdate(input) {
|
|
|
4020
4051
|
return { status: exitCode === 0 ? "ok" : "fail" };
|
|
4021
4052
|
}
|
|
4022
4053
|
//#endregion
|
|
4054
|
+
//#region src/env.ts
|
|
4055
|
+
function createEnvLookup(source = process.env) {
|
|
4056
|
+
return {
|
|
4057
|
+
get(key) {
|
|
4058
|
+
return source[key] || void 0;
|
|
4059
|
+
},
|
|
4060
|
+
first(...keys) {
|
|
4061
|
+
for (const key of keys) {
|
|
4062
|
+
const val = source[key];
|
|
4063
|
+
if (val) return val;
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4068
|
+
//#endregion
|
|
4069
|
+
//#region src/auth-resolver.ts
|
|
4070
|
+
/**
|
|
4071
|
+
* Build a strict, single-feature token resolver.
|
|
4072
|
+
*
|
|
4073
|
+
* Resolution chain: `--token flag → envName env var → keyring(keyringKey)`.
|
|
4074
|
+
* No broad-token fallback — if the feature-specific token is absent the
|
|
4075
|
+
* operation fails with a message naming the exact env var to set.
|
|
4076
|
+
*/
|
|
4077
|
+
function createFeatureResolver(config) {
|
|
4078
|
+
return function resolveFeatureToken(input = {}) {
|
|
4079
|
+
const env = createEnvLookup(input.env);
|
|
4080
|
+
const keyring = input.keyring ?? getToken;
|
|
4081
|
+
const token = input.cliToken || env.get(config.envName) || keyring(config.keyringKey);
|
|
4082
|
+
if (!token) throw new AuthError(`no GitHub token found for this operation. Pass --token <PAT>, set ${config.envName}, or run: holocron auth set ${config.keyringKey} <PAT>`);
|
|
4083
|
+
return token;
|
|
4084
|
+
};
|
|
4085
|
+
}
|
|
4086
|
+
//#endregion
|
|
4023
4087
|
//#region src/commands/sync.ts
|
|
4024
4088
|
const SYNC_STEPS = [
|
|
4025
4089
|
"labels",
|
|
@@ -4564,6 +4628,14 @@ async function checkForUpdates(currentVersion) {
|
|
|
4564
4628
|
}
|
|
4565
4629
|
//#endregion
|
|
4566
4630
|
//#region src/cli.ts
|
|
4631
|
+
const resolveCloneToken = createFeatureResolver({
|
|
4632
|
+
envName: "HOLOCRON_READ_TOKEN",
|
|
4633
|
+
keyringKey: "github.read"
|
|
4634
|
+
});
|
|
4635
|
+
const resolveSyncToken = createFeatureResolver({
|
|
4636
|
+
envName: "HOLOCRON_SYNC_TOKEN",
|
|
4637
|
+
keyringKey: "github.sync"
|
|
4638
|
+
});
|
|
4567
4639
|
const { version: CLI_VERSION } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
4568
4640
|
/** Parses --token values and returns the context spread, or null on parse error (exits with code 1). */
|
|
4569
4641
|
function tokenContext(rawTokens) {
|
|
@@ -4604,9 +4676,11 @@ await yargs(hideBin(process.argv)).scriptName("").usage("holocron <command> [opt
|
|
|
4604
4676
|
}), async (argv) => {
|
|
4605
4677
|
const tokens = tokenContext(argv.token);
|
|
4606
4678
|
if (!tokens) return;
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4679
|
+
let token;
|
|
4680
|
+
try {
|
|
4681
|
+
token = resolveCloneToken({ cliToken: tokens.cliTokens?.["github"] ?? tokens.cliToken });
|
|
4682
|
+
} catch (err) {
|
|
4683
|
+
console.error(`clone: ${err instanceof AuthError ? err.message : String(err)}`);
|
|
4610
4684
|
process.exitCode = 1;
|
|
4611
4685
|
return;
|
|
4612
4686
|
}
|
|
@@ -4829,9 +4903,12 @@ await yargs(hideBin(process.argv)).scriptName("").usage("holocron <command> [opt
|
|
|
4829
4903
|
const outputDir = argv["output-dir"];
|
|
4830
4904
|
const parsed = tokenContext(argv.token);
|
|
4831
4905
|
if (!parsed) return;
|
|
4832
|
-
|
|
4833
|
-
if (
|
|
4834
|
-
|
|
4906
|
+
let token;
|
|
4907
|
+
if (outputDir) token = "no-token-needed";
|
|
4908
|
+
else try {
|
|
4909
|
+
token = resolveSyncToken({ cliToken: parsed.cliTokens?.["github"] ?? parsed.cliToken });
|
|
4910
|
+
} catch (err) {
|
|
4911
|
+
console.error(`sync-github: ${err instanceof AuthError ? err.message : String(err)}`);
|
|
4835
4912
|
process.exitCode = 1;
|
|
4836
4913
|
return;
|
|
4837
4914
|
}
|
|
@@ -5009,13 +5086,13 @@ await yargs(hideBin(process.argv)).scriptName("").usage("holocron <command> [opt
|
|
|
5009
5086
|
if (report.message) console.error(`upgrade node: ${report.message}`);
|
|
5010
5087
|
process.exitCode = 1;
|
|
5011
5088
|
}
|
|
5012
|
-
}).demandCommand(1, "Run `holocron upgrade --help` to see available upgrade subcommands."), () => {}).command("auth <subcommand>", "Manage bootstrap credentials in the OS keyring", (y) => y.command("set <provider> [
|
|
5089
|
+
}).demandCommand(1, "Run `holocron upgrade --help` to see available upgrade subcommands."), () => {}).command("auth <subcommand>", "Manage bootstrap credentials in the OS keyring", (y) => y.command("set <provider> [value]", "Verify + store a bootstrap token for a provider", (yy) => yy.positional("provider", {
|
|
5013
5090
|
type: "string",
|
|
5014
5091
|
demandOption: true
|
|
5015
|
-
}).positional("
|
|
5092
|
+
}).positional("value", { type: "string" }), async (argv) => {
|
|
5016
5093
|
if ((await runAuthSet({
|
|
5017
5094
|
provider: argv.provider,
|
|
5018
|
-
...argv.
|
|
5095
|
+
...argv.value ? { positional: argv.value } : {}
|
|
5019
5096
|
})).status === "fail") process.exitCode = 1;
|
|
5020
5097
|
}).command("unset <provider>", "Remove a stored bootstrap token", (yy) => yy.positional("provider", {
|
|
5021
5098
|
type: "string",
|
package/dist/index.d.mts
CHANGED
|
@@ -4,7 +4,21 @@ import { AuthError, RequestOptions, ResolveTokenConfig as ResolveTokenConfig$1,
|
|
|
4
4
|
type ResolveTokenConfig = Omit<ResolveTokenConfig$1, "getKeyringToken">;
|
|
5
5
|
/** Wraps `createResolveToken` from `@theholocron/http` and injects the
|
|
6
6
|
* system keyring so plugins stay at a one-liner call site. */
|
|
7
|
-
declare function createResolveToken(config: ResolveTokenConfig): (input?:
|
|
7
|
+
declare function createResolveToken(config: ResolveTokenConfig): (input?: ResolveTokenInput) => string;
|
|
8
|
+
interface FeatureResolverConfig {
|
|
9
|
+
/** Env var name for this feature, e.g. `"HOLOCRON_ISSUES_TOKEN"`. */
|
|
10
|
+
envName: string;
|
|
11
|
+
/** Keyring account key, e.g. `"github.issues"`. */
|
|
12
|
+
keyringKey: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build a strict, single-feature token resolver.
|
|
16
|
+
*
|
|
17
|
+
* Resolution chain: `--token flag → envName env var → keyring(keyringKey)`.
|
|
18
|
+
* No broad-token fallback — if the feature-specific token is absent the
|
|
19
|
+
* operation fails with a message naming the exact env var to set.
|
|
20
|
+
*/
|
|
21
|
+
declare function createFeatureResolver(config: FeatureResolverConfig): (input?: ResolveTokenInput) => string;
|
|
8
22
|
//#endregion
|
|
9
23
|
//#region src/config.d.ts
|
|
10
24
|
type ProviderOptions = Record<string, unknown>;
|
|
@@ -158,6 +172,13 @@ declare function resolveConfig(raw: HolocronConfig): ResolvedHolocronConfig;
|
|
|
158
172
|
//#region src/define-config.d.ts
|
|
159
173
|
declare function defineConfig(config: HolocronConfig): HolocronConfig;
|
|
160
174
|
//#endregion
|
|
175
|
+
//#region src/env.d.ts
|
|
176
|
+
interface EnvLookup {
|
|
177
|
+
get(key: string): string | undefined;
|
|
178
|
+
first(...keys: string[]): string | undefined;
|
|
179
|
+
}
|
|
180
|
+
declare function createEnvLookup(source?: NodeJS.ProcessEnv): EnvLookup;
|
|
181
|
+
//#endregion
|
|
161
182
|
//#region src/keyring.d.ts
|
|
162
183
|
/**
|
|
163
184
|
* Keyring-backed bootstrap credential store.
|
|
@@ -218,4 +239,4 @@ interface LoadedConfig {
|
|
|
218
239
|
*/
|
|
219
240
|
declare function loadConfig(cwd: string): Promise<LoadedConfig>;
|
|
220
241
|
//#endregion
|
|
221
|
-
export { Analytics, AppConfig, Auth, AuthDescription, AuthError, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityConfigPackage, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConfigError, ConfigFileError, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, DoctorConfig, EnsureResult, Environment, EnvironmentReviewer, Environments, HolocronConfig, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, LoadedConfig, MultiEntry, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, REQUIRED_CAPABILITIES, RawProviderEntry, RawProvidersConfig, RepoConfig, RepoProperties, RepoProtection, RepoRef, RepoSettings, type RequestOptions, ResolveTokenConfig, type ResolveTokenInput, ResolvedCapability, ResolvedHolocronConfig, ResolvedProviderEntry, ResolvedProvidersConfig, ResolvedTuple, type RestClient, type RestClientConfig, Ruleset, SecretScope, Secrets, SingleEntry, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, createResolveToken, createRestClient, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
|
|
242
|
+
export { Analytics, AppConfig, Auth, AuthDescription, AuthError, AuthEvent, AuthEventType, AuthIdentity, AuthUser, CARDINALITY, CapabilityConfigPackage, CapabilityImpls, CapabilityKey, Cardinality, CardinalityFor, Ci, CiRun, CiRunFilter, CiRunStatus, ConfigError, ConfigFileError, ConnectionStringOptions, CreateAuthUserInput, Deployment, DeploymentProject, DeploymentProjectSettings, DeploymentRecord, DeploymentTarget, DeploymentTrigger, Dns, DnsRecord, DnsRecordType, DoctorConfig, EnsureResult, EnvLookup, Environment, EnvironmentReviewer, Environments, FeatureResolverConfig, HolocronConfig, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, LoadedConfig, MultiEntry, NormalizedAuthUser, Notifications, Observability, ParseWebhookInput, ProviderApiError, ProviderIdentity, ProviderOptions, REQUIRED_CAPABILITIES, RawProviderEntry, RawProvidersConfig, RepoConfig, RepoProperties, RepoProtection, RepoRef, RepoSettings, type RequestOptions, ResolveTokenConfig, type ResolveTokenInput, ResolvedCapability, ResolvedHolocronConfig, ResolvedProviderEntry, ResolvedProvidersConfig, ResolvedTuple, type RestClient, type RestClientConfig, Ruleset, SecretScope, Secrets, SingleEntry, Source, StatusCategory, Storage, StorageBranch, TeamEntry, TeamPermission, Tooling, ToolingDoctorReport, TrackerDoctorReport, TrackerUser, Vault, WebhookDashboardInfo, WebhookVerificationError, createEnvLookup, createFeatureResolver, createResolveToken, createRestClient, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
|
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,21 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
6
6
|
import { basename, dirname, join } from "node:path";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
|
+
//#region src/env.ts
|
|
10
|
+
function createEnvLookup(source = process.env) {
|
|
11
|
+
return {
|
|
12
|
+
get(key) {
|
|
13
|
+
return source[key] || void 0;
|
|
14
|
+
},
|
|
15
|
+
first(...keys) {
|
|
16
|
+
for (const key of keys) {
|
|
17
|
+
const val = source[key];
|
|
18
|
+
if (val) return val;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
9
24
|
//#region src/keyring.ts
|
|
10
25
|
/**
|
|
11
26
|
* Keyring-backed bootstrap credential store.
|
|
@@ -85,6 +100,22 @@ function createResolveToken(config) {
|
|
|
85
100
|
getKeyringToken: getToken
|
|
86
101
|
});
|
|
87
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Build a strict, single-feature token resolver.
|
|
105
|
+
*
|
|
106
|
+
* Resolution chain: `--token flag → envName env var → keyring(keyringKey)`.
|
|
107
|
+
* No broad-token fallback — if the feature-specific token is absent the
|
|
108
|
+
* operation fails with a message naming the exact env var to set.
|
|
109
|
+
*/
|
|
110
|
+
function createFeatureResolver(config) {
|
|
111
|
+
return function resolveFeatureToken(input = {}) {
|
|
112
|
+
const env = createEnvLookup(input.env);
|
|
113
|
+
const keyring = input.keyring ?? getToken;
|
|
114
|
+
const token = input.cliToken || env.get(config.envName) || keyring(config.keyringKey);
|
|
115
|
+
if (!token) throw new AuthError(`no GitHub token found for this operation. Pass --token <PAT>, set ${config.envName}, or run: holocron auth set ${config.keyringKey} <PAT>`);
|
|
116
|
+
return token;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
88
119
|
//#endregion
|
|
89
120
|
//#region src/config.ts
|
|
90
121
|
/**
|
|
@@ -325,4 +356,4 @@ async function fileExists(path) {
|
|
|
325
356
|
}
|
|
326
357
|
}
|
|
327
358
|
//#endregion
|
|
328
|
-
export { AuthError, CARDINALITY, ConfigError, ConfigFileError, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, createResolveToken, createRestClient, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
|
|
359
|
+
export { AuthError, CARDINALITY, ConfigError, ConfigFileError, ProviderApiError, REQUIRED_CAPABILITIES, WebhookVerificationError, createEnvLookup, createFeatureResolver, createResolveToken, createRestClient, defineConfig, deleteToken, getToken, isMulti, listStoredProviders, loadConfig, resolveConfig, resolveEntry, resolvePluginPackage, setToken };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "The Holocron CLI — a pluggable, capability-based orchestrator for spinning up and operating software projects.",
|
|
5
5
|
"homepage": "https://github.com/theholocron/holocron/tree/main/packages/cli#readme",
|
|
6
6
|
"bugs": "https://github.com/theholocron/holocron/issues",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"author": "Newton Koumantzelis",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"main": "./dist/index.mjs",
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
16
17
|
"bin": {
|
|
17
18
|
"holocron": "./dist/cli.mjs"
|
|
18
19
|
},
|
|
@@ -34,17 +35,17 @@
|
|
|
34
35
|
],
|
|
35
36
|
"dependencies": {
|
|
36
37
|
"@napi-rs/keyring": "^1.3.0",
|
|
37
|
-
"@theholocron/github-client": "^1.3.
|
|
38
|
-
"@theholocron/http-client": "^1.3.
|
|
38
|
+
"@theholocron/github-client": "^1.3.3",
|
|
39
|
+
"@theholocron/http-client": "^1.3.3",
|
|
39
40
|
"chalk": "^5.6.2",
|
|
40
41
|
"ora": "^9.4.1",
|
|
41
|
-
"tsx": "4.
|
|
42
|
+
"tsx": "4.23.1",
|
|
42
43
|
"yargs": "^18.0.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"@theholocron/eslint-config": "^7.
|
|
46
|
-
"@theholocron/tsconfig": "^7.
|
|
47
|
-
"@theholocron/vitest-config": "^7.
|
|
46
|
+
"@theholocron/eslint-config": "^7.5.0",
|
|
47
|
+
"@theholocron/tsconfig": "^7.5.0",
|
|
48
|
+
"@theholocron/vitest-config": "^7.5.0",
|
|
48
49
|
"@types/node": "^26",
|
|
49
50
|
"@types/yargs": "^17.0.35",
|
|
50
51
|
"@vitest/coverage-v8": "^4.1.10",
|
|
@@ -67,6 +68,5 @@
|
|
|
67
68
|
"test": "vitest run",
|
|
68
69
|
"test:watch": "vitest",
|
|
69
70
|
"test:coverage": "vitest run --coverage"
|
|
70
|
-
}
|
|
71
|
-
"types": "./dist/index.d.mts"
|
|
71
|
+
}
|
|
72
72
|
}
|