@theholocron/astromech 5.0.0-alpha.154 → 5.0.0-alpha.156
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 +2 -2
- package/dist/config/index.d.mts +17 -17
- package/dist/config/index.mjs +3 -3
- package/dist/index.d.mts +455 -455
- package/dist/index.mjs +382 -370
- package/dist/lint/index.d.mts +11 -11
- package/dist/lint/index.mjs +7 -7
- package/dist/{load-ToVjaKPr.mjs → load-DdHEVFlG.mjs} +3 -3
- package/dist/{schema.type-BUiSrwS5.d.mts → schema.type-B9k8EoOb.d.mts} +1 -1
- package/package.json +9 -9
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as normalizeTaskEntry, t as loadTasksConfig } from "./load-
|
|
1
|
+
import { r as normalizeTaskEntry, t as loadTasksConfig } from "./load-DdHEVFlG.mjs";
|
|
2
2
|
import { n as alexrc_default, t as editorconfig_checker_default } from "./editorconfig-checker-DPejtR5z.mjs";
|
|
3
3
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { dependencyNames, detectPackageManager, listWorkspacePackagesSync, readPackageJsonSync } from "@theholocron/pkg-utils";
|
|
@@ -9,7 +9,7 @@ import { findExecutableSync } from "@theholocron/fs-utils";
|
|
|
9
9
|
//#region src/templates/codecov/codecov.yml
|
|
10
10
|
var codecov_default = "codecov:\n require_ci_to_pass: true\n\ncoverage:\n precision: 2\n round: down\n status:\n project:\n default:\n target: auto\n threshold: 2%\n # Infra-only PRs (dep bumps, config, lockfile) touch no coverable code,\n # so codecov produces no report to compare. Pass instead of blocking\n # forever on a required check that will never post.\n if_not_found: success\n patch:\n default:\n target: 80%\n if_not_found: success\n\ncomment:\n layout: \"reach,diff,flags,components\"\n behavior: default\n require_changes: true\n\ncomponent_management:\n default_rules:\n statuses:\n - type: patch\n target: 80%\n if_not_found: success\n individual_components:\n";
|
|
11
11
|
//#endregion
|
|
12
|
-
//#region src/generators/codecov.ts
|
|
12
|
+
//#region src/generators/codecov/codecov.ts
|
|
13
13
|
/**
|
|
14
14
|
* `codecov.yml` generation — a manifest-derived artifact (component `paths`
|
|
15
15
|
* come from workspace packages; the status targets track the `test` task's
|
|
@@ -22,7 +22,7 @@ const INDIVIDUAL_COMPONENTS_MARKER = " individual_components:";
|
|
|
22
22
|
function scaffoldHeader() {
|
|
23
23
|
return [
|
|
24
24
|
`# Scaffolded by holocron repo setup — edit this file freely.`,
|
|
25
|
-
`# Source: theholocron/holocron · packages/astromech/src/generators/codecov.ts`,
|
|
25
|
+
`# Source: theholocron/holocron · packages/astromech/src/generators/codecov/codecov.ts`,
|
|
26
26
|
``
|
|
27
27
|
].join("\n");
|
|
28
28
|
}
|
|
@@ -122,7 +122,7 @@ var holocron_default = "name: Holocron\ndescription: >\n Run the Holocron CLI
|
|
|
122
122
|
var install_default = "name: Install dependencies\ndescription: Install project dependencies with pnpm frozen lockfile.\n\nruns:\n using: composite\n\n steps:\n - name: Install dependencies\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n shell: bash\n run: pnpm install --frozen-lockfile\n";
|
|
123
123
|
//#endregion
|
|
124
124
|
//#region src/templates/reusable/actions/setup.yml
|
|
125
|
-
var setup_default = "name: Setup\ndescription: Prepare the environment and install project dependencies.\n\ninputs:\n node-version:\n description: Node.js version\n required: false\n default: \"22.x\"\n\nruns:\n using: composite\n\n steps:\n - uses: theholocron/.github/.github/actions/setup-node@main\n with:\n node-version: ${{ inputs.node-version }}\n\n - uses: theholocron/.github/.github/actions/install@main\n\n # `@theholocron/cli`'s `dist/cli.mjs` doesn't exist yet at this point, so\n # pnpm's workspace bin-linking can't create `node_modules/.bin/holocron`\n # (silent `WARN ... ENOENT`, not a hard failure). Every package whose\n # scripts delegate via `holocron run <task> --` then fails at run time\n # with `holocron: not found` (#698). Same fix the `holocron` composite\n # action already applies for its own direct invocation (see its \"Build\n # the workspace when the CLI resolves to an unbuilt checkout\" step) —\n # mirrored here so it also covers *nested* `holocron run <task> --`\n # calls from inside package.json scripts, which that action's own guard\n # doesn't reach. `pnpm build` (not a scoped `--filter`) so the CLI's own\n # workspace dependencies (e.g.
|
|
125
|
+
var setup_default = "name: Setup\ndescription: Prepare the environment and install project dependencies.\n\ninputs:\n node-version:\n description: Node.js version\n required: false\n default: \"22.x\"\n\nruns:\n using: composite\n\n steps:\n - uses: theholocron/.github/.github/actions/setup-node@main\n with:\n node-version: ${{ inputs.node-version }}\n\n - uses: theholocron/.github/.github/actions/install@main\n\n # `@theholocron/cli`'s `dist/cli.mjs` doesn't exist yet at this point, so\n # pnpm's workspace bin-linking can't create `node_modules/.bin/holocron`\n # (silent `WARN ... ENOENT`, not a hard failure). Every package whose\n # scripts delegate via `holocron run <task> --` then fails at run time\n # with `holocron: not found` (#698). Same fix the `holocron` composite\n # action already applies for its own direct invocation (see its \"Build\n # the workspace when the CLI resolves to an unbuilt checkout\" step) —\n # mirrored here so it also covers *nested* `holocron run <task> --`\n # calls from inside package.json scripts, which that action's own guard\n # doesn't reach. `pnpm build` (not a scoped `--filter`) so the CLI's own\n # workspace dependencies (e.g. astromech) build\n # in topological order too — a scoped build skips them and fails.\n # No-op in every consumer repo, where the published tarball already\n # ships `dist/`.\n - name: Build the Holocron CLI\n shell: bash\n run: |\n if [ ! -f node_modules/@theholocron/cli/dist/cli.mjs ]; then\n echo \"→ @theholocron/cli dist/ missing — building the workspace from source\" >&2\n pnpm build\n echo \"→ re-linking node_modules/.bin — pnpm skipped it pre-build\" >&2\n pnpm install --frozen-lockfile\n fi\n\n - name: Cache Turbo artifacts\n if: ${{ hashFiles('turbo.json') != '' }}\n uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0\n with:\n path: .turbo\n key: turbo-${{ runner.os }}-${{ github.sha }}\n restore-keys: |\n turbo-${{ runner.os }}-\n";
|
|
126
126
|
//#endregion
|
|
127
127
|
//#region src/templates/reusable/actions/setup-node.yml
|
|
128
128
|
var setup_node_default = "name: Setup Node\ndescription: Install pnpm and Node.js with pnpm dependency caching.\n\ninputs:\n node-version:\n description: Node.js version\n required: false\n default: \"22.x\"\nruns:\n using: composite\n\n steps:\n - name: Setup pnpm\n if: ${{ hashFiles('pnpm-lock.yaml') != '' }}\n uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4\n\n - name: Setup Node.js\n uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0\n with:\n node-version: ${{ hashFiles('.node-version') != '' && '' || inputs.node-version }}\n node-version-file: ${{ hashFiles('.node-version') != '' && '.node-version' || '' }}\n cache: ${{ hashFiles('pnpm-lock.yaml') != '' && 'pnpm' || '' }}\n # Do NOT add registry-url here. setup-node writes .npmrc with\n # _authToken=${NODE_AUTH_TOKEN} and sets NPM_CONFIG_USERCONFIG to it.\n # pnpm reads that file, fails env-var substitution when the token is\n # empty, and loses auth entirely. Without registry-url, pnpm 10.15+\n # handles Trusted Publishing via its own native OIDC exchange.\n\n - name: Add node_modules/.bin to PATH\n shell: bash\n run: echo \"$GITHUB_WORKSPACE/node_modules/.bin\" >> $GITHUB_PATH\n";
|
|
@@ -217,7 +217,7 @@ var verification_typeSafety_default$1 = "name: Typecheck\n\non: # yamllint disab
|
|
|
217
217
|
//#region src/templates/reusable/verification.unitTests.yml
|
|
218
218
|
var verification_unitTests_default$1 = "name: Test\n\non: # yamllint disable-line rule:truthy\n workflow_call:\n inputs:\n run-unit:\n description: Run unit tests with coverage (disable for UI-only repos that use Storybook testing exclusively)\n type: boolean\n required: false\n default: true\n run-storybook:\n description: Run Storybook vitest interaction tests (requires .storybook/ setup)\n type: boolean\n required: false\n default: false\n run-interaction:\n description: Run Storybook interaction and accessibility tests with Playwright\n type: boolean\n required: false\n default: false\n run-chromatic:\n description: Publish Storybook to Chromatic for visual regression testing\n type: boolean\n required: false\n default: false\n chromatic-projects:\n description: >\n JSON array of Chromatic projects to build, one matrix job per entry.\n Each entry: { \"tokenName\": \"WEB\", \"workingDir\": \"apps/web\", \"buildScript\": \"build:storybook\" }.\n tokenName maps to secret CHROMATIC_PROJECT_TOKEN_<TOKENNAME>; use \"default\"\n (or omit for the legacy empty-string form) to use the bare CHROMATIC_PROJECT_TOKEN\n secret for single-project repos.\n buildScript defaults to \"build:storybook\" when omitted.\n Default runs a single job from the repo root using CHROMATIC_PROJECT_TOKEN.\n type: string\n required: false\n default: '[{\"tokenName\":\"default\",\"workingDir\":\".\",\"buildScript\":\"build:storybook\"}]'\n run-user-flow:\n description: Run Cypress E2E user-flow tests (requires cypress.config.*)\n type: boolean\n required: false\n default: false\n wait-on-url:\n description: URL to wait for before running Cypress tests (default is Vite dev server; override for non-Vite stacks e.g. http://localhost:3000 for Next.js)\n type: string\n required: false\n default: \"http://localhost:5173\"\n secrets:\n CHROMATIC_PROJECT_TOKEN:\n required: false\n CYPRESS_RECORD_KEY:\n required: false\n TURBO_TOKEN:\n required: false\n\njobs:\n unit:\n name: Run unit tests and collect coverage\n if: ${{ inputs.run-unit }}\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 - uses: theholocron/.github/.github/actions/holocron@main\n name: Run tests with coverage\n with:\n task: verification.unitTests\n\n # A Codecov upload must not fail silently. fail_ci_if_error makes a failed\n # upload fail this step (the action's default is to log the error and pass),\n # which fails the job and so the `Test / Conclusion` check. Whether that\n # blocks a merge is up to whether the check is required, not this workflow\n # (holocron#934).\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload coverage to Codecov\n with:\n use_oidc: true\n fail_ci_if_error: true\n\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload test results to Codecov\n if: ${{ !cancelled() }}\n with:\n use_oidc: true\n fail_ci_if_error: true\n report_type: test_results\n files: \"**/test-report.junit.xml\"\n\n storybook:\n name: Run Storybook interaction tests\n if: ${{ inputs.run-storybook }}\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 exec playwright install chromium --with-deps\n name: Install Playwright\n\n - run: pnpm test:storybook\n name: Run Storybook tests\n\n # A Codecov upload must not fail silently. fail_ci_if_error makes a failed\n # upload fail this step (the action's default is to log the error and pass),\n # which fails the job and so the `Test / Conclusion` check. Whether that\n # blocks a merge is up to whether the check is required, not this workflow\n # (holocron#934).\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload coverage to Codecov\n with:\n use_oidc: true\n fail_ci_if_error: true\n\n - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0\n name: Upload test results to Codecov\n if: ${{ !cancelled() }}\n with:\n use_oidc: true\n fail_ci_if_error: true\n report_type: test_results\n files: \"**/test-report.junit.xml\"\n\n visual-and-composition:\n # No explicit matrix reference here — a skipped job (run-chromatic:\n # false, the common case) never expands its matrix, so a `${{\n # matrix.project.tokenName }}` in name: has nothing to resolve and\n # shows as raw template text in the checks UI. GitHub auto-appends\n # the matrix values in parens when the matrix *does* expand, so this\n # loses no distinctness across multiple Chromatic projects.\n name: Run visual and composition tests\n if: ${{ inputs.run-chromatic }}\n strategy:\n fail-fast: false\n matrix:\n project: ${{ fromJSON(inputs.chromatic-projects) }}\n permissions:\n contents: read\n statuses: write\n runs-on: ubuntu-latest\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 - uses: chromaui/action@14cfaef73576e69f95f47f60058063f46ca38719 # v18\n name: Publish to Chromatic\n with:\n projectToken: ${{ (matrix.project.tokenName == 'default' || matrix.project.tokenName == '') && secrets.CHROMATIC_PROJECT_TOKEN || secrets[format('CHROMATIC_PROJECT_TOKEN_{0}', matrix.project.tokenName)] }}\n token: ${{ github.token }}\n buildScriptName: ${{ matrix.project.buildScript || 'build:storybook' }}\n workingDir: ${{ matrix.project.workingDir || '.' }}\n storybookBaseDir: ${{ matrix.project.storybookBaseDir || '' }}\n untraced: ${{ matrix.project.untraced || '' }}\n onlyStoryFiles: ${{ matrix.project.onlyStoryFiles || '' }}\n exitZeroOnChanges: ${{ matrix.project.exitZeroOnChanges || false }}\n\n interaction-and-accessibility:\n name: Run interactions and accessibility tests\n if: ${{ inputs.run-interaction }}\n permissions:\n contents: read\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 exec playwright install --with-deps\n name: Install Playwright\n\n - run: pnpm test:storybook\n name: Run interaction and accessibility tests\n\n user-flow:\n name: Run user flow tests\n if: ${{ inputs.run-user-flow }}\n permissions:\n contents: read\n runs-on: ubuntu-latest\n timeout-minutes: 15\n strategy:\n fail-fast: false\n matrix:\n containers: [1, 2]\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 exec cypress install\n name: Install Cypress binary\n\n - uses: cypress-io/github-action@1052aa98bbbe4f55210f844878213c07d9c8c399 # v6.7.13\n name: Cypress run\n with:\n start: pnpm dev\n wait-on: ${{ inputs.wait-on-url }}\n record: true\n parallel: true\n env:\n CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n conclusion:\n name: Conclusion\n runs-on: ubuntu-latest\n if: always()\n needs: [unit, storybook, visual-and-composition, interaction-and-accessibility, user-flow]\n steps:\n - name: Check job statuses\n run: |\n if [[ \"$RESULTS\" == *\"failure\"* ]] || [[ \"$RESULTS\" == *\"cancelled\"* ]]; then\n exit 1\n fi\n env:\n RESULTS: ${{ join(needs.*.result, ',') }}\n";
|
|
219
219
|
//#endregion
|
|
220
|
-
//#region src/generators/github-action-thin-callers.ts
|
|
220
|
+
//#region src/generators/github-action-thin-callers/github-action-thin-callers.ts
|
|
221
221
|
/**
|
|
222
222
|
* Workflow templates + thin-caller generation.
|
|
223
223
|
*
|
|
@@ -244,7 +244,7 @@ const WORKFLOW_TEMPLATES = {
|
|
|
244
244
|
"platform.repoSync": "name: Platform\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n paths:\n - holocron.config.ts\n - package.json\n - pnpm-workspace.yaml\n workflow_dispatch:\n inputs:\n steps:\n description: \"Sync steps to run (default: all)\"\n type: string\n required: false\n\nconcurrency:\n group: sync-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n sync:\n name: Sync\n uses: theholocron/.github/.github/workflows/platform.repoSync.yml@main\n with:\n steps: ${{ inputs.steps }}\n secrets: inherit\n",
|
|
245
245
|
"platform.commitStandards": "name: Platform\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\nconcurrency:\n group: platform-commit-standards-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n\njobs:\n commit-standards:\n name: Commit Standards\n uses: theholocron/.github/.github/workflows/platform.commitStandards.yml@main\n secrets: inherit\n",
|
|
246
246
|
"platform.repoValidation": "name: Platform\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n repo-validation:\n name: Repo Validation\n uses: theholocron/.github/.github/workflows/platform.repoValidation.yml@main\n secrets: inherit\n",
|
|
247
|
-
"platform.syncTemplates": "name: Platform\n\n# Bootstrap note: this `uses:` a reusable workflow that must already exist\n# on theholocron/.github's main branch to resolve at all — a brand-new\n# reusable workflow here needs ONE manual `holocron sync github` run\n# (pushing directly to theholocron/.github) before this thin caller can\n# ever successfully trigger it, since GitHub resolves `uses:` at run time,\n# not at merge time. Same deadlock every reusable workflow added here hits\n# on its first introduction.\non: # yamllint disable-line rule:truthy\n push:\n # main only — this publishes to theholocron/.github, consumed by every\n # other repo's CI. alpha is the prerelease channel; publishing from it\n # would let unreleased/unstable template changes overwrite what main\n # just published, the same stable-vs-prerelease split this org already\n # enforces for npm's dist-tags and Sentinel's own deploy trigger.\n branches: [main]\n paths:\n - packages/astromech/src/templates/**\n - packages/astromech/src/generators/**\n - packages/astromech/src/registry
|
|
247
|
+
"platform.syncTemplates": "name: Platform\n\n# Bootstrap note: this `uses:` a reusable workflow that must already exist\n# on theholocron/.github's main branch to resolve at all — a brand-new\n# reusable workflow here needs ONE manual `holocron sync github` run\n# (pushing directly to theholocron/.github) before this thin caller can\n# ever successfully trigger it, since GitHub resolves `uses:` at run time,\n# not at merge time. Same deadlock every reusable workflow added here hits\n# on its first introduction.\non: # yamllint disable-line rule:truthy\n push:\n # main only — this publishes to theholocron/.github, consumed by every\n # other repo's CI. alpha is the prerelease channel; publishing from it\n # would let unreleased/unstable template changes overwrite what main\n # just published, the same stable-vs-prerelease split this org already\n # enforces for npm's dist-tags and Sentinel's own deploy trigger.\n branches: [main]\n paths:\n - packages/astromech/src/templates/**\n - packages/astromech/src/generators/**\n - packages/astromech/src/registry/**\n - packages/cli/src/templates/**\n workflow_dispatch: {}\n\nconcurrency:\n group: sync-templates\n cancel-in-progress: false\n\npermissions:\n contents: read\n\njobs:\n sync-templates:\n name: Sync Templates\n # workflow_dispatch can target ANY ref, not only main — without this,\n # anyone able to dispatch this workflow (a broader group than who can\n # push to main) could run it against an arbitrary branch, checking out\n # and building that ref's own code with the sync token present, then\n # publishing that ref's templates to theholocron/.github.\n if: github.ref == 'refs/heads/main'\n uses: theholocron/.github/.github/workflows/platform.syncTemplates.yml@main\n secrets: inherit\n",
|
|
248
248
|
"knowledge.wiki": "name: Knowledge\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n pull_request:\n branches: [main]\n\nconcurrency:\n group: ${{ github.event_name == 'pull_request' && format('wiki-preview-{0}', github.event.pull_request.number) || 'wiki' }}\n cancel-in-progress: ${{ github.event_name == 'pull_request' }}\n\npermissions:\n contents: read\n deployments: write\n\njobs:\n publish:\n name: Publish\n if: ${{ github.event_name != 'pull_request' }}\n uses: theholocron/.github/.github/workflows/knowledge.wiki.yml@main\n secrets: inherit\n\n preview:\n name: Preview\n if: ${{ github.event_name == 'pull_request' }}\n uses: theholocron/.github/.github/workflows/knowledge.wiki.yml@main\n with:\n preview: true\n preview-id: pr-${{ github.event.pull_request.number }}\n secrets: inherit\n",
|
|
249
249
|
preview: "name: Preview\n\non: # yamllint disable-line rule:truthy\n pull_request:\n branches: [main]\n\nconcurrency:\n group: preview-${{ github.event.pull_request.number }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n deployments: write\n pull-requests: write\n\njobs:\n preview:\n name: Preview\n uses: theholocron/.github/.github/workflows/preview.yml@main\n secrets: inherit\n",
|
|
250
250
|
review: "name: Source Quality\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\nconcurrency:\n group: review-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n checks: write\n pull-requests: write\n\njobs:\n review:\n name: Annotations\n uses: theholocron/.github/.github/workflows/review.yml@main\n secrets: inherit\n",
|
|
@@ -504,7 +504,7 @@ function deriveDeployPaths(raw) {
|
|
|
504
504
|
return paths;
|
|
505
505
|
}
|
|
506
506
|
//#endregion
|
|
507
|
-
//#region src/generators/github-action-reusable-workflows.ts
|
|
507
|
+
//#region src/generators/github-action-reusable-workflows/github-action-reusable-workflows.ts
|
|
508
508
|
/**
|
|
509
509
|
* The **reusable** GitHub Actions surface pushed to `theholocron/.github` by
|
|
510
510
|
* `holocron sync github` — the `workflow_call` implementations and composite
|
|
@@ -565,7 +565,7 @@ const WORKFLOW_TEMPLATE_PROPERTIES = { bookkeeping: JSON.stringify({
|
|
|
565
565
|
description: "Label and track issues and pull requests.",
|
|
566
566
|
iconName: "octicon tag"
|
|
567
567
|
}, null, 2) };
|
|
568
|
-
const SOURCE = "packages/astromech/src/generators/github-action-reusable-workflows.ts";
|
|
568
|
+
const SOURCE = "packages/astromech/src/generators/github-action-reusable-workflows/github-action-reusable-workflows.ts";
|
|
569
569
|
/**
|
|
570
570
|
* "AUTO-GENERATED — do not edit" header for the YAML pushed to
|
|
571
571
|
* `theholocron/.github`. Matches `createHeader().workflowHeader({ forPrimary: true })`
|
|
@@ -599,7 +599,7 @@ function reusableTemplates() {
|
|
|
599
599
|
return out;
|
|
600
600
|
}
|
|
601
601
|
//#endregion
|
|
602
|
-
//#region src/registry.ts
|
|
602
|
+
//#region src/registry/registry.ts
|
|
603
603
|
/**
|
|
604
604
|
* The task registry — how each task runs *locally*, without GitHub
|
|
605
605
|
* Actions. `holocron run <task>` and `holocron run ci` resolve against this;
|
|
@@ -813,7 +813,7 @@ const CI_ORDER = [
|
|
|
813
813
|
"delivery.deploy"
|
|
814
814
|
];
|
|
815
815
|
//#endregion
|
|
816
|
-
//#region src/generators/required-checks.ts
|
|
816
|
+
//#region src/generators/required-checks/required-checks.ts
|
|
817
817
|
/**
|
|
818
818
|
* `requiredChecks(config)` — the branch-protection required-status-check list,
|
|
819
819
|
* derived from the task manifest. Every `required: true` task contributes its
|
|
@@ -843,7 +843,75 @@ function requiredChecks(config) {
|
|
|
843
843
|
return out;
|
|
844
844
|
}
|
|
845
845
|
//#endregion
|
|
846
|
-
//#region src/generators/
|
|
846
|
+
//#region src/generators/tsconfig/tsconfig.ts
|
|
847
|
+
/**
|
|
848
|
+
* `tsconfig.json` generation — Bucket B (config-resolution workstream, #676):
|
|
849
|
+
* the file must stay committed (the TypeScript language server discovers it
|
|
850
|
+
* directly from disk, no `--config` override mechanism to hand it a path
|
|
851
|
+
* instead), but its content is fully derivable, so it's generated rather
|
|
852
|
+
* than hand-authored — the same category as `.editorconfig`
|
|
853
|
+
* ({@link "../../templates/configs/editorconfig/create-config.js" createConfig})
|
|
854
|
+
* and {@link codecovConfig}.
|
|
855
|
+
*
|
|
856
|
+
* Surveyed every package-level `tsconfig.json` across the org (`holocron`,
|
|
857
|
+
* `clients`, `utils` — 20+ packages). `extends: "@theholocron/tsconfig/
|
|
858
|
+
* node-lts"`, `compilerOptions.baseUrl: "./"`, `compilerOptions.outDir:
|
|
859
|
+
* "./dist"`, `include: ["src/**\/*.ts"]`, and `exclude: ["node_modules",
|
|
860
|
+
* "dist"]` are uniform in every one — safe defaults here. `paths` (a `@/*`
|
|
861
|
+
* → `./src/*` import alias) is a real per-package choice, present in
|
|
862
|
+
* roughly 40% of packages checked — an opt-in parameter, not a default.
|
|
863
|
+
*
|
|
864
|
+
* Two fields checked and deliberately *not* absorbed as defaults, unlike
|
|
865
|
+
* `eslint-config`'s `tsconfigRootDir`/`settings.node` (which turned out
|
|
866
|
+
* redundant with the shared package's own behavior, `configs`#461):
|
|
867
|
+
* `compilerOptions.module`/`moduleResolution` (only `@theholocron/cli`
|
|
868
|
+
* overrides these, to `esnext`/`bundler` — a genuine deviation from the
|
|
869
|
+
* shared `node-lts` preset's `nodenext`/`nodenext`, needed for its bundler
|
|
870
|
+
* tooling, not something every package should inherit) and
|
|
871
|
+
* `compilerOptions.rootDir` (present in exactly two packages, always the
|
|
872
|
+
* same value `./src` that `include` already implies — an unnecessary
|
|
873
|
+
* override wherever it appears, not a pattern worth generalizing).
|
|
874
|
+
*
|
|
875
|
+
* Monorepo *root* `tsconfig.json` (a TS project-references "solution
|
|
876
|
+
* file" — `holocron`'s own root is `{ files: [], references: [...] }`,
|
|
877
|
+
* listing which packages to build) is a structurally different, genuinely
|
|
878
|
+
* per-repo document — out of scope here, same as Bucket C content.
|
|
879
|
+
*
|
|
880
|
+
* Not yet wired into `holocron repo setup`'s per-package write loop (every
|
|
881
|
+
* other Bucket B file there is a single repo-root file, safely
|
|
882
|
+
* overwritten every run — `tsconfig.json` is per-package, and most
|
|
883
|
+
* packages' files still carry real hand-authored content today, not yet
|
|
884
|
+
* migrated to a fully-generated state). That per-package iteration +
|
|
885
|
+
* safe-migration design is `#680`'s job, not duplicated here — this is
|
|
886
|
+
* the generator itself, ready for it to call.
|
|
887
|
+
*/
|
|
888
|
+
/**
|
|
889
|
+
* A package-level `tsconfig.json` — the uniform shape confirmed across
|
|
890
|
+
* every package checked, parameterized only by the two fields that
|
|
891
|
+
* genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
|
|
892
|
+
* `.alexrc.json`'s existing precedent for a strict-JSON Bucket B file —
|
|
893
|
+
* JSON has no comment syntax to carry one, and TypeScript's own tolerance
|
|
894
|
+
* for JSONC comments in `tsconfig.json` isn't worth relying on for a file
|
|
895
|
+
* this thin).
|
|
896
|
+
*/
|
|
897
|
+
function createTsconfig(options) {
|
|
898
|
+
const variant = options.variant ?? "node-lts";
|
|
899
|
+
const compilerOptions = {
|
|
900
|
+
baseUrl: "./",
|
|
901
|
+
outDir: "./dist"
|
|
902
|
+
};
|
|
903
|
+
if (options.paths) compilerOptions.paths = { "@/*": ["./src/*"] };
|
|
904
|
+
const config = {
|
|
905
|
+
display: options.display,
|
|
906
|
+
extends: `@theholocron/tsconfig/${variant}`,
|
|
907
|
+
compilerOptions,
|
|
908
|
+
include: ["src/**/*.ts"],
|
|
909
|
+
exclude: ["node_modules", "dist"]
|
|
910
|
+
};
|
|
911
|
+
return stringifyJson(config);
|
|
912
|
+
}
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/generators/turbo/turbo.ts
|
|
847
915
|
/**
|
|
848
916
|
* `turboConfig(config)` — the generated `turbo.json` for a repo's task
|
|
849
917
|
* manifest, epic #672 D9 (#681). Every workspace, monorepo or single-package
|
|
@@ -1033,7 +1101,7 @@ function ensureTurboDependency(packageJson, workspaceYaml) {
|
|
|
1033
1101
|
};
|
|
1034
1102
|
}
|
|
1035
1103
|
//#endregion
|
|
1036
|
-
//#region src/tasks/run/linters.ts
|
|
1104
|
+
//#region src/tasks/run/linters/linters.ts
|
|
1037
1105
|
/**
|
|
1038
1106
|
* The linter registry — one list of linter names (from `config.tasks`'
|
|
1039
1107
|
* `{ name: "lint", linters: [...] }`, or auto-detected) maps to both the
|
|
@@ -1187,14 +1255,14 @@ function resolveLinters(opts) {
|
|
|
1187
1255
|
}));
|
|
1188
1256
|
}
|
|
1189
1257
|
//#endregion
|
|
1190
|
-
//#region src/tasks/run/resolve-bin.ts
|
|
1258
|
+
//#region src/tasks/run/resolve-bin/resolve-bin.ts
|
|
1191
1259
|
/** `<cwd>/node_modules/.bin/<tool>` when it exists, else the bare `tool` name (left to `PATH`). */
|
|
1192
1260
|
function resolveBin(cwd, tool, fileExists) {
|
|
1193
1261
|
const local = join(cwd, "node_modules", ".bin", tool);
|
|
1194
1262
|
return fileExists(local) ? local : tool;
|
|
1195
1263
|
}
|
|
1196
1264
|
//#endregion
|
|
1197
|
-
//#region src/tasks/run/resolve-runner.ts
|
|
1265
|
+
//#region src/tasks/run/resolve-runner/resolve-runner.ts
|
|
1198
1266
|
const mkRunner = (tool, args = []) => ({
|
|
1199
1267
|
tool,
|
|
1200
1268
|
args
|
|
@@ -1241,7 +1309,7 @@ function resolveRunner(local, cwd, listDir, readFile) {
|
|
|
1241
1309
|
for (const candidate of local.detect) if (files.some((f) => candidate.when.test(f)) || deps.has(candidate.tool)) return mkRunner(candidate.tool, candidate.args);
|
|
1242
1310
|
}
|
|
1243
1311
|
//#endregion
|
|
1244
|
-
//#region src/tasks/run/resolve-export-entry.ts
|
|
1312
|
+
//#region src/tasks/run/resolve-export-entry/resolve-export-entry.ts
|
|
1245
1313
|
/**
|
|
1246
1314
|
* Unwrap a `package.json#exports` subpath entry down to its file path —
|
|
1247
1315
|
* either a bare string, or an object with condition keys (`import`,
|
|
@@ -1259,7 +1327,7 @@ function resolveExportEntry(entry) {
|
|
|
1259
1327
|
if (typeof value === "object" && value !== null) return resolveExportEntry(value);
|
|
1260
1328
|
}
|
|
1261
1329
|
//#endregion
|
|
1262
|
-
//#region src/tasks/run/tool-configs.ts
|
|
1330
|
+
//#region src/tasks/run/tool-configs/tool-configs.ts
|
|
1263
1331
|
/**
|
|
1264
1332
|
* `library` is every mapped tool's org-wide default variant today — every
|
|
1265
1333
|
* currently-migrated repo (`holocron`, `clients`, `utils`, `configs`,
|
|
@@ -1337,7 +1405,7 @@ const LOCAL_CONFIG_FILES = {
|
|
|
1337
1405
|
]
|
|
1338
1406
|
};
|
|
1339
1407
|
//#endregion
|
|
1340
|
-
//#region src/tasks/run/resolve-tool-config.ts
|
|
1408
|
+
//#region src/tasks/run/resolve-tool-config/resolve-tool-config.ts
|
|
1341
1409
|
/**
|
|
1342
1410
|
* Resolves the absolute path to a Bucket A tool's shared `@theholocron/*-config`
|
|
1343
1411
|
* entry point, installed in the consuming repo's own `node_modules` — and the
|
|
@@ -1401,7 +1469,7 @@ function resolveToolConfig(tool, cwd, deps) {
|
|
|
1401
1469
|
return [...spec.flag, absolute];
|
|
1402
1470
|
}
|
|
1403
1471
|
//#endregion
|
|
1404
|
-
//#region src/tasks/run/turbo-defines-task.ts
|
|
1472
|
+
//#region src/tasks/run/turbo-defines-task/turbo-defines-task.ts
|
|
1405
1473
|
/** `true` when `<cwd>/turbo.json` declares `task` (under `tasks`, or turbo 1.x's `pipeline`). */
|
|
1406
1474
|
function turboDefinesTask(cwd, task, readFile, fileExists) {
|
|
1407
1475
|
if (!fileExists(join(cwd, "turbo.json"))) return false;
|
|
@@ -1746,7 +1814,7 @@ function runLinterGroup(input, names, passthrough) {
|
|
|
1746
1814
|
};
|
|
1747
1815
|
}
|
|
1748
1816
|
//#endregion
|
|
1749
|
-
//#region src/tasks/ci.ts
|
|
1817
|
+
//#region src/tasks/ci/ci.ts
|
|
1750
1818
|
/**
|
|
1751
1819
|
* `holocron run ci` / `astromech.ci()` — run the merge-gating checks locally, in CI
|
|
1752
1820
|
* order, and exit non-zero on the first failure. The "will CI pass?" command a
|
|
@@ -1816,71 +1884,85 @@ function taskName(item) {
|
|
|
1816
1884
|
return typeof item === "string" ? item : item.name;
|
|
1817
1885
|
}
|
|
1818
1886
|
//#endregion
|
|
1819
|
-
//#region src/
|
|
1820
|
-
/**
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
function
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
dryRun: input.dryRun ?? false,
|
|
1853
|
-
...input.filter ? { filter: input.filter } : {}
|
|
1854
|
-
});
|
|
1855
|
-
jobs.push({
|
|
1856
|
-
task: name,
|
|
1857
|
-
status: r.status,
|
|
1858
|
-
command: r.command,
|
|
1859
|
-
message: r.message
|
|
1860
|
-
});
|
|
1861
|
-
}
|
|
1887
|
+
//#region src/utils/create-header/create-header.ts
|
|
1888
|
+
/**
|
|
1889
|
+
* Factory that binds `source` and `tool` once and returns all header
|
|
1890
|
+
* constructors for auto-generated files. Prefer this over passing source
|
|
1891
|
+
* as a positional argument at every call site.
|
|
1892
|
+
*
|
|
1893
|
+
* @example
|
|
1894
|
+
* const { workflowHeader, scaffoldHeader } = createHeader({
|
|
1895
|
+
* source: "packages/astromech/src/templates/configs/editorconfig/create-config.ts",
|
|
1896
|
+
* });
|
|
1897
|
+
*
|
|
1898
|
+
* export function createConfig(): string {
|
|
1899
|
+
* return `${workflowHeader()}${body}`;
|
|
1900
|
+
* }
|
|
1901
|
+
*
|
|
1902
|
+
* ## Which header to use
|
|
1903
|
+
*
|
|
1904
|
+
* | Function | When to use | User can edit? |
|
|
1905
|
+
* |-------------------|----------------------------------------------------|----------------|
|
|
1906
|
+
* | `workflowHeader` | Files overwritten on every setup / sync-github run | No — "AUTO-GENERATED — do not edit" |
|
|
1907
|
+
* | `scaffoldHeader` | Files written once; user takes ownership after | Yes — "Scaffolded — edit this file freely" |
|
|
1908
|
+
*
|
|
1909
|
+
* ## `workflowHeader` formats
|
|
1910
|
+
*
|
|
1911
|
+
* | Format | Comment style | Used for |
|
|
1912
|
+
* |---------------------|------------------------|-------------------------------------------------------|
|
|
1913
|
+
* | `"yaml"` (default) | `# …` lines | YAML workflows, ignore files, `.editorconfig`, labeler|
|
|
1914
|
+
* | `"cjs"` | `/* … *\/` block | CommonJS config files (`.cjs`) |
|
|
1915
|
+
* | `"shebang"` | `#!/bin/sh` + `# …` | Shell scripts (`prepare-commit-msg`, hooks) |
|
|
1916
|
+
*/
|
|
1917
|
+
function createHeader(options) {
|
|
1918
|
+
const { source, tool = "holocron repo setup", forPrimary = false } = options;
|
|
1919
|
+
const doNotEdit = forPrimary ? `AUTO-GENERATED — do not edit in theholocron/.github directly.` : `AUTO-GENERATED — do not edit directly.`;
|
|
1862
1920
|
return {
|
|
1863
|
-
|
|
1864
|
-
|
|
1921
|
+
workflowHeader(format = "yaml") {
|
|
1922
|
+
if (format === "cjs") return [
|
|
1923
|
+
`/* ${doNotEdit}`,
|
|
1924
|
+
` * Source: theholocron/holocron · ${source}`,
|
|
1925
|
+
` * Tool: ${tool}`,
|
|
1926
|
+
` * Changes: edit source in theholocron/holocron`,
|
|
1927
|
+
` */`,
|
|
1928
|
+
``
|
|
1929
|
+
].join("\n");
|
|
1930
|
+
const yamlLines = [
|
|
1931
|
+
`# ${doNotEdit}`,
|
|
1932
|
+
`# Source: theholocron/holocron · ${source}`,
|
|
1933
|
+
`# Tool: ${tool}`,
|
|
1934
|
+
`# Changes: edit source in theholocron/holocron`,
|
|
1935
|
+
``
|
|
1936
|
+
].join("\n");
|
|
1937
|
+
if (format === "shebang") return `#!/bin/sh\n\n${yamlLines}`;
|
|
1938
|
+
return yamlLines;
|
|
1939
|
+
},
|
|
1940
|
+
scaffoldHeader() {
|
|
1941
|
+
return [
|
|
1942
|
+
`# Scaffolded by holocron repo setup — edit this file freely.`,
|
|
1943
|
+
`# Source: theholocron/holocron · ${source}`,
|
|
1944
|
+
``
|
|
1945
|
+
].join("\n");
|
|
1946
|
+
}
|
|
1865
1947
|
};
|
|
1866
1948
|
}
|
|
1867
1949
|
//#endregion
|
|
1868
|
-
//#region src/utils/default-list-dirs.ts
|
|
1950
|
+
//#region src/utils/default-list-dirs/default-list-dirs.ts
|
|
1869
1951
|
const defaultListDirs = (path) => readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1870
1952
|
//#endregion
|
|
1871
|
-
//#region src/utils/default-read-json.ts
|
|
1953
|
+
//#region src/utils/default-read-json/default-read-json.ts
|
|
1872
1954
|
const defaultReadJson = (path) => JSON.parse(readFileSync(path, "utf8"));
|
|
1873
1955
|
//#endregion
|
|
1874
|
-
//#region src/utils/default-run.ts
|
|
1956
|
+
//#region src/utils/default-run/default-run.ts
|
|
1875
1957
|
const defaultRun = (command, args, opts) => spawnSync(command, args, {
|
|
1876
1958
|
encoding: "utf8",
|
|
1877
1959
|
...opts
|
|
1878
1960
|
});
|
|
1879
1961
|
//#endregion
|
|
1880
|
-
//#region src/utils/default-sleep.ts
|
|
1962
|
+
//#region src/utils/default-sleep/default-sleep.ts
|
|
1881
1963
|
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1882
1964
|
//#endregion
|
|
1883
|
-
//#region src/utils/exec.ts
|
|
1965
|
+
//#region src/utils/exec/exec.ts
|
|
1884
1966
|
/** Runs `cmd` with the parent's stdio; a signal-killed or unspawnable process reports `-1`. */
|
|
1885
1967
|
const realExec = (cmd, args, opts) => {
|
|
1886
1968
|
return { exitCode: spawnSync(cmd, args, {
|
|
@@ -1889,262 +1971,76 @@ const realExec = (cmd, args, opts) => {
|
|
|
1889
1971
|
}).status ?? -1 };
|
|
1890
1972
|
};
|
|
1891
1973
|
//#endregion
|
|
1892
|
-
//#region src/utils/look-path.ts
|
|
1974
|
+
//#region src/utils/look-path/look-path.ts
|
|
1893
1975
|
/** `node_modules/.bin/<bin>`, else the first `PATH` entry that has it, else `null`. */
|
|
1894
1976
|
const realLookPath = (cwd, bin) => findExecutableSync(bin, { path: join(cwd, "node_modules", ".bin") }) ?? findExecutableSync(bin) ?? null;
|
|
1895
1977
|
//#endregion
|
|
1896
|
-
//#region src/utils/noop-logger.ts
|
|
1978
|
+
//#region src/utils/noop-logger/noop-logger.ts
|
|
1897
1979
|
/** The `RunLogger` used when the caller injects none: drops every record. */
|
|
1898
1980
|
const noopLogger = {
|
|
1899
1981
|
debug() {},
|
|
1900
1982
|
warn() {}
|
|
1901
1983
|
};
|
|
1902
1984
|
//#endregion
|
|
1903
|
-
//#region src/
|
|
1985
|
+
//#region src/tasks/deploy-on-release/constants.ts
|
|
1986
|
+
/** A stable release (no prerelease identifier) is on `main`, the default channel for a task. */
|
|
1987
|
+
const MAIN_CHANNEL = "main";
|
|
1988
|
+
const DEPLOY_TASK = "delivery.deploy";
|
|
1989
|
+
/** How long to wait for npm to serve the release's versions, and how often to ask. */
|
|
1990
|
+
const WAIT_TIMEOUT_MS = 3e5;
|
|
1991
|
+
const WAIT_INTERVAL_MS = 1e4;
|
|
1992
|
+
//#endregion
|
|
1993
|
+
//#region src/tasks/deploy-on-release/workspace-dependencies/workspace-dependencies.ts
|
|
1994
|
+
/** The `workspace:*` dependencies of `pkg`, as workspace packages (with their current version). */
|
|
1995
|
+
function workspaceDependencies(pkg, all) {
|
|
1996
|
+
const names = new Set(Object.entries(pkg.dependencies).filter(([, spec]) => String(spec).startsWith("workspace:")).map(([name]) => name));
|
|
1997
|
+
return all.filter((candidate) => names.has(candidate.name));
|
|
1998
|
+
}
|
|
1999
|
+
//#endregion
|
|
2000
|
+
//#region src/tasks/deploy-on-release/default-paths/default-paths.ts
|
|
1904
2001
|
/**
|
|
1905
|
-
* `
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
2002
|
+
* The paths a release must touch to redeploy `pkg` when its manifest doesn't
|
|
2003
|
+
* list them: its own folder plus the folder of every
|
|
2004
|
+
* {@link workspaceDependencies} entry. Derived rather than listed so a new or
|
|
2005
|
+
* dropped workspace dependency needs no second edit.
|
|
2006
|
+
*
|
|
2007
|
+
* @returns Repo-relative prefixes, each ending in `/`.
|
|
1908
2008
|
*/
|
|
1909
|
-
function
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2009
|
+
function defaultPaths(repoRoot, pkg, all) {
|
|
2010
|
+
return [pkg, ...workspaceDependencies(pkg, all)].map(({ dir }) => `${relative(repoRoot, dir)}/`);
|
|
2011
|
+
}
|
|
2012
|
+
//#endregion
|
|
2013
|
+
//#region src/tasks/deploy-on-release/deploy-targets/deploy-targets.ts
|
|
2014
|
+
/**
|
|
2015
|
+
* The packages whose manifest declares a release-time `delivery.deploy`
|
|
2016
|
+
* (`with.on === "release"`), with the task's options resolved.
|
|
2017
|
+
*/
|
|
2018
|
+
function deployTargets(packages, manifests) {
|
|
2019
|
+
return packages.flatMap((pkg) => (manifests.get(pkg.dir)?.tasks ?? []).flatMap((item) => {
|
|
2020
|
+
const entry = normalizeTaskEntry(item);
|
|
2021
|
+
if (entry.name !== "delivery.deploy" || entry.with?.["on"] !== "release") return [];
|
|
2022
|
+
const { channel, paths } = entry.with;
|
|
2023
|
+
return [{
|
|
2024
|
+
pkg,
|
|
2025
|
+
channel: typeof channel === "string" ? channel : MAIN_CHANNEL,
|
|
2026
|
+
paths: Array.isArray(paths) ? paths.map(String) : void 0
|
|
2027
|
+
}];
|
|
2028
|
+
}));
|
|
2029
|
+
}
|
|
2030
|
+
//#endregion
|
|
2031
|
+
//#region src/tasks/deploy-on-release/should-deploy/should-deploy.ts
|
|
2032
|
+
/**
|
|
2033
|
+
* @param channel The release's channel: its prerelease identifier (`alpha`), or `main` for a stable release.
|
|
2034
|
+
* @param changedFiles `undefined` when there's no previous release to diff against.
|
|
2035
|
+
*/
|
|
2036
|
+
function shouldDeploy({ channel, configuredChannel, changedFiles, paths }) {
|
|
2037
|
+
if (channel !== configuredChannel) return {
|
|
2038
|
+
deploy: false,
|
|
2039
|
+
reason: `channel "${channel}" isn't "${configuredChannel}"`
|
|
1918
2040
|
};
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
* {@link packageScripts} and by `holocron`'s own interactive task picker.
|
|
1923
|
-
* Deliberately independent of `syncScripts`/`holocronScript`/`hooks`:
|
|
1924
|
-
* those govern whether/how `package.json` scripts get *written*, not
|
|
1925
|
-
* which tasks exist to run. A repo with `syncScripts: false` (this repo
|
|
1926
|
-
* itself, among others) still has real runnable tasks — found live when
|
|
1927
|
-
* `packageScripts()`'s `{}` short-circuit silently emptied the picker too.
|
|
1928
|
-
*/
|
|
1929
|
-
const runnableTaskNames = () => {
|
|
1930
|
-
const out = [];
|
|
1931
|
-
for (const entry of items()) {
|
|
1932
|
-
const def = TASKS[entry.name];
|
|
1933
|
-
const hasNoLocalRunner = def?.local === null && !def.linterGroup && !def.jobs;
|
|
1934
|
-
if (entry.local === false || !KNOWN_TASKS.has(entry.name) || hasNoLocalRunner) continue;
|
|
1935
|
-
out.push(entry.name);
|
|
1936
|
-
}
|
|
1937
|
-
return out;
|
|
1938
|
-
};
|
|
1939
|
-
return {
|
|
1940
|
-
run: (task, opts = {}) => {
|
|
1941
|
-
if (!TASKS[task] && options.config && matchNamespace(options.config, task).length > 0) return runGroup({
|
|
1942
|
-
...deps,
|
|
1943
|
-
cwd: options.cwd,
|
|
1944
|
-
config: options.config,
|
|
1945
|
-
namespace: task,
|
|
1946
|
-
dryRun: opts.dryRun ?? false,
|
|
1947
|
-
...opts.filter ? { filter: opts.filter } : {}
|
|
1948
|
-
});
|
|
1949
|
-
return runTask({
|
|
1950
|
-
...deps,
|
|
1951
|
-
task,
|
|
1952
|
-
cwd: options.cwd,
|
|
1953
|
-
...opts.job !== void 0 ? { job: opts.job } : {},
|
|
1954
|
-
passthrough: opts.passthrough ?? [],
|
|
1955
|
-
dryRun: opts.dryRun ?? false,
|
|
1956
|
-
required: opts.required ?? false,
|
|
1957
|
-
...opts.filter ? { filter: opts.filter } : {}
|
|
1958
|
-
});
|
|
1959
|
-
},
|
|
1960
|
-
ci: (opts = {}) => runCi({
|
|
1961
|
-
...deps,
|
|
1962
|
-
cwd: options.cwd,
|
|
1963
|
-
config: options.config ?? {},
|
|
1964
|
-
...opts
|
|
1965
|
-
}),
|
|
1966
|
-
thinCallers: () => {
|
|
1967
|
-
const orgCtx = options.orgContext ?? {};
|
|
1968
|
-
const out = /* @__PURE__ */ new Map();
|
|
1969
|
-
for (const entry of items()) {
|
|
1970
|
-
const isDocsSite = entry.name === "knowledge.docs";
|
|
1971
|
-
const isComponents = entry.name === "knowledge.components";
|
|
1972
|
-
if (entry.ci === false || !KNOWN_WORKFLOWS.has(entry.name) && !isDocsSite && !isComponents) continue;
|
|
1973
|
-
if (entry.name === "delivery.deploy" && entry.with?.["on"] === "release") continue;
|
|
1974
|
-
const rawWith = isDocsSite ? {
|
|
1975
|
-
preview: true,
|
|
1976
|
-
...entry.with,
|
|
1977
|
-
docs: entry.with?.["docs"] ?? true
|
|
1978
|
-
} : isComponents ? {
|
|
1979
|
-
preview: true,
|
|
1980
|
-
...entry.with,
|
|
1981
|
-
storybook: entry.with?.["storybook"] ?? [{ workingDir: "." }]
|
|
1982
|
-
} : entry.with;
|
|
1983
|
-
const withOverrides = rawWith ? normalizeWorkflowWith(rawWith) : void 0;
|
|
1984
|
-
if (entry.name === "verification.unitTests" && withOverrides?.["run-unit"] === false && withOverrides?.["run-storybook"] === false) throw new Error("test workflow: at least one of \"run-unit\" or \"run-storybook\" must be true. Library repos use run-unit: true; UI/Storybook repos use run-storybook: true.");
|
|
1985
|
-
const isDeployFamily = entry.name === "delivery.deploy" || isDocsSite || isComponents;
|
|
1986
|
-
const additionalPaths = entry.paths ?? (isDeployFamily && rawWith ? deriveDeployPaths(rawWith) : void 0);
|
|
1987
|
-
if (isDeployFamily && rawWith) {
|
|
1988
|
-
const preview = extractPreviewConfig(rawWith, orgCtx);
|
|
1989
|
-
if (preview) {
|
|
1990
|
-
const deployWith = normalizeWorkflowWith(rawWith);
|
|
1991
|
-
const deployPaths = entry.paths ?? deriveDeployPaths(rawWith);
|
|
1992
|
-
out.set(`${entry.name}.yml`, generateCombinedDeployContent(deployWith, deployPaths, preview));
|
|
1993
|
-
continue;
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
out.set(`${entry.name}.yml`, generateThinCallerContent(entry.name, withOverrides, additionalPaths, deps.logger));
|
|
1997
|
-
}
|
|
1998
|
-
return out;
|
|
1999
|
-
},
|
|
2000
|
-
packageScripts: () => {
|
|
2001
|
-
const out = {};
|
|
2002
|
-
if (!options.config || options.config.syncScripts === false) return out;
|
|
2003
|
-
out.holocron = options.config.holocronScript ?? "holocron";
|
|
2004
|
-
for (const name of runnableTaskNames()) out[name] = `holocron run ${name} --`;
|
|
2005
|
-
const hooks = options.config.hooks;
|
|
2006
|
-
if (hooks === true || typeof hooks === "object" && hooks.prePush !== false) out.prepare = "husky";
|
|
2007
|
-
return out;
|
|
2008
|
-
},
|
|
2009
|
-
runnableTasks: () => runnableTaskNames(),
|
|
2010
|
-
reusableTemplates: () => reusableTemplates(),
|
|
2011
|
-
requiredChecks: () => requiredChecks(options.config ?? {}),
|
|
2012
|
-
codecovConfig: (existing) => codecovConfig(options.cwd, existing),
|
|
2013
|
-
turboConfig: () => turboConfig(options.config ?? {}),
|
|
2014
|
-
ensureRootWorkspaceMember: (workspaceYaml, rootScripts) => {
|
|
2015
|
-
return ensureRootWorkspaceMember(workspaceYaml, rootScripts, (options.config?.tasks ?? []).map(normalizeTaskEntry).filter((e) => e.local !== false).map((e) => e.name));
|
|
2016
|
-
},
|
|
2017
|
-
ensureTurboDependency: (packageJson, workspaceYaml) => ensureTurboDependency(packageJson, workspaceYaml)
|
|
2018
|
-
};
|
|
2019
|
-
}
|
|
2020
|
-
//#endregion
|
|
2021
|
-
//#region src/generators/tsconfig.ts
|
|
2022
|
-
/**
|
|
2023
|
-
* `tsconfig.json` generation — Bucket B (config-resolution workstream, #676):
|
|
2024
|
-
* the file must stay committed (the TypeScript language server discovers it
|
|
2025
|
-
* directly from disk, no `--config` override mechanism to hand it a path
|
|
2026
|
-
* instead), but its content is fully derivable, so it's generated rather
|
|
2027
|
-
* than hand-authored — the same category as `.editorconfig`
|
|
2028
|
-
* ({@link "./templates/configs/editorconfig/create-config.js" createConfig})
|
|
2029
|
-
* and {@link codecovConfig}.
|
|
2030
|
-
*
|
|
2031
|
-
* Surveyed every package-level `tsconfig.json` across the org (`holocron`,
|
|
2032
|
-
* `clients`, `utils` — 20+ packages). `extends: "@theholocron/tsconfig/
|
|
2033
|
-
* node-lts"`, `compilerOptions.baseUrl: "./"`, `compilerOptions.outDir:
|
|
2034
|
-
* "./dist"`, `include: ["src/**\/*.ts"]`, and `exclude: ["node_modules",
|
|
2035
|
-
* "dist"]` are uniform in every one — safe defaults here. `paths` (a `@/*`
|
|
2036
|
-
* → `./src/*` import alias) is a real per-package choice, present in
|
|
2037
|
-
* roughly 40% of packages checked — an opt-in parameter, not a default.
|
|
2038
|
-
*
|
|
2039
|
-
* Two fields checked and deliberately *not* absorbed as defaults, unlike
|
|
2040
|
-
* `eslint-config`'s `tsconfigRootDir`/`settings.node` (which turned out
|
|
2041
|
-
* redundant with the shared package's own behavior, `configs`#461):
|
|
2042
|
-
* `compilerOptions.module`/`moduleResolution` (only `@theholocron/cli`
|
|
2043
|
-
* overrides these, to `esnext`/`bundler` — a genuine deviation from the
|
|
2044
|
-
* shared `node-lts` preset's `nodenext`/`nodenext`, needed for its bundler
|
|
2045
|
-
* tooling, not something every package should inherit) and
|
|
2046
|
-
* `compilerOptions.rootDir` (present in exactly two packages, always the
|
|
2047
|
-
* same value `./src` that `include` already implies — an unnecessary
|
|
2048
|
-
* override wherever it appears, not a pattern worth generalizing).
|
|
2049
|
-
*
|
|
2050
|
-
* Monorepo *root* `tsconfig.json` (a TS project-references "solution
|
|
2051
|
-
* file" — `holocron`'s own root is `{ files: [], references: [...] }`,
|
|
2052
|
-
* listing which packages to build) is a structurally different, genuinely
|
|
2053
|
-
* per-repo document — out of scope here, same as Bucket C content.
|
|
2054
|
-
*
|
|
2055
|
-
* Not yet wired into `holocron repo setup`'s per-package write loop (every
|
|
2056
|
-
* other Bucket B file there is a single repo-root file, safely
|
|
2057
|
-
* overwritten every run — `tsconfig.json` is per-package, and most
|
|
2058
|
-
* packages' files still carry real hand-authored content today, not yet
|
|
2059
|
-
* migrated to a fully-generated state). That per-package iteration +
|
|
2060
|
-
* safe-migration design is `#680`'s job, not duplicated here — this is
|
|
2061
|
-
* the generator itself, ready for it to call.
|
|
2062
|
-
*/
|
|
2063
|
-
/**
|
|
2064
|
-
* A package-level `tsconfig.json` — the uniform shape confirmed across
|
|
2065
|
-
* every package checked, parameterized only by the two fields that
|
|
2066
|
-
* genuinely vary (`display`, `paths`). No scaffold/workflow header (matches
|
|
2067
|
-
* `.alexrc.json`'s existing precedent for a strict-JSON Bucket B file —
|
|
2068
|
-
* JSON has no comment syntax to carry one, and TypeScript's own tolerance
|
|
2069
|
-
* for JSONC comments in `tsconfig.json` isn't worth relying on for a file
|
|
2070
|
-
* this thin).
|
|
2071
|
-
*/
|
|
2072
|
-
function createTsconfig(options) {
|
|
2073
|
-
const variant = options.variant ?? "node-lts";
|
|
2074
|
-
const compilerOptions = {
|
|
2075
|
-
baseUrl: "./",
|
|
2076
|
-
outDir: "./dist"
|
|
2077
|
-
};
|
|
2078
|
-
if (options.paths) compilerOptions.paths = { "@/*": ["./src/*"] };
|
|
2079
|
-
const config = {
|
|
2080
|
-
display: options.display,
|
|
2081
|
-
extends: `@theholocron/tsconfig/${variant}`,
|
|
2082
|
-
compilerOptions,
|
|
2083
|
-
include: ["src/**/*.ts"],
|
|
2084
|
-
exclude: ["node_modules", "dist"]
|
|
2085
|
-
};
|
|
2086
|
-
return stringifyJson(config);
|
|
2087
|
-
}
|
|
2088
|
-
//#endregion
|
|
2089
|
-
//#region src/tasks/deploy-on-release/constants.ts
|
|
2090
|
-
/** A stable release (no prerelease identifier) is on `main`, the default channel for a task. */
|
|
2091
|
-
const MAIN_CHANNEL = "main";
|
|
2092
|
-
const DEPLOY_TASK = "delivery.deploy";
|
|
2093
|
-
/** How long to wait for npm to serve the release's versions, and how often to ask. */
|
|
2094
|
-
const WAIT_TIMEOUT_MS = 3e5;
|
|
2095
|
-
const WAIT_INTERVAL_MS = 1e4;
|
|
2096
|
-
//#endregion
|
|
2097
|
-
//#region src/tasks/deploy-on-release/workspace-dependencies.ts
|
|
2098
|
-
/** The `workspace:*` dependencies of `pkg`, as workspace packages (with their current version). */
|
|
2099
|
-
function workspaceDependencies(pkg, all) {
|
|
2100
|
-
const names = new Set(Object.entries(pkg.dependencies).filter(([, spec]) => String(spec).startsWith("workspace:")).map(([name]) => name));
|
|
2101
|
-
return all.filter((candidate) => names.has(candidate.name));
|
|
2102
|
-
}
|
|
2103
|
-
//#endregion
|
|
2104
|
-
//#region src/tasks/deploy-on-release/default-paths.ts
|
|
2105
|
-
/**
|
|
2106
|
-
* The paths a release must touch to redeploy `pkg` when its manifest doesn't
|
|
2107
|
-
* list them: its own folder plus the folder of every
|
|
2108
|
-
* {@link workspaceDependencies} entry. Derived rather than listed so a new or
|
|
2109
|
-
* dropped workspace dependency needs no second edit.
|
|
2110
|
-
*
|
|
2111
|
-
* @returns Repo-relative prefixes, each ending in `/`.
|
|
2112
|
-
*/
|
|
2113
|
-
function defaultPaths(repoRoot, pkg, all) {
|
|
2114
|
-
return [pkg, ...workspaceDependencies(pkg, all)].map(({ dir }) => `${relative(repoRoot, dir)}/`);
|
|
2115
|
-
}
|
|
2116
|
-
//#endregion
|
|
2117
|
-
//#region src/tasks/deploy-on-release/deploy-targets.ts
|
|
2118
|
-
/**
|
|
2119
|
-
* The packages whose manifest declares a release-time `delivery.deploy`
|
|
2120
|
-
* (`with.on === "release"`), with the task's options resolved.
|
|
2121
|
-
*/
|
|
2122
|
-
function deployTargets(packages, manifests) {
|
|
2123
|
-
return packages.flatMap((pkg) => (manifests.get(pkg.dir)?.tasks ?? []).flatMap((item) => {
|
|
2124
|
-
const entry = normalizeTaskEntry(item);
|
|
2125
|
-
if (entry.name !== "delivery.deploy" || entry.with?.["on"] !== "release") return [];
|
|
2126
|
-
const { channel, paths } = entry.with;
|
|
2127
|
-
return [{
|
|
2128
|
-
pkg,
|
|
2129
|
-
channel: typeof channel === "string" ? channel : MAIN_CHANNEL,
|
|
2130
|
-
paths: Array.isArray(paths) ? paths.map(String) : void 0
|
|
2131
|
-
}];
|
|
2132
|
-
}));
|
|
2133
|
-
}
|
|
2134
|
-
//#endregion
|
|
2135
|
-
//#region src/tasks/deploy-on-release/should-deploy.ts
|
|
2136
|
-
/**
|
|
2137
|
-
* @param channel The release's channel: its prerelease identifier (`alpha`), or `main` for a stable release.
|
|
2138
|
-
* @param changedFiles `undefined` when there's no previous release to diff against.
|
|
2139
|
-
*/
|
|
2140
|
-
function shouldDeploy({ channel, configuredChannel, changedFiles, paths }) {
|
|
2141
|
-
if (channel !== configuredChannel) return {
|
|
2142
|
-
deploy: false,
|
|
2143
|
-
reason: `channel "${channel}" isn't "${configuredChannel}"`
|
|
2144
|
-
};
|
|
2145
|
-
if (changedFiles === void 0) return {
|
|
2146
|
-
deploy: true,
|
|
2147
|
-
reason: "no previous release to diff against"
|
|
2041
|
+
if (changedFiles === void 0) return {
|
|
2042
|
+
deploy: true,
|
|
2043
|
+
reason: "no previous release to diff against"
|
|
2148
2044
|
};
|
|
2149
2045
|
const touched = changedFiles.find((file) => paths.some((path) => file.startsWith(path)));
|
|
2150
2046
|
return touched ? {
|
|
@@ -2156,7 +2052,7 @@ function shouldDeploy({ channel, configuredChannel, changedFiles, paths }) {
|
|
|
2156
2052
|
};
|
|
2157
2053
|
}
|
|
2158
2054
|
//#endregion
|
|
2159
|
-
//#region src/tasks/deploy-on-release/wait-for-published.ts
|
|
2055
|
+
//#region src/tasks/deploy-on-release/wait-for-published/wait-for-published.ts
|
|
2160
2056
|
/**
|
|
2161
2057
|
* Waits until npm serves every `name@version` in `packages`, asking with
|
|
2162
2058
|
* `npm view --prefer-online` (so npm's local cache can't answer for the
|
|
@@ -2183,7 +2079,7 @@ async function waitForPublished({ packages, run, sleep, now, cwd, timeoutMs = WA
|
|
|
2183
2079
|
}
|
|
2184
2080
|
}
|
|
2185
2081
|
//#endregion
|
|
2186
|
-
//#region src/tasks/deploy-on-release/workspace-packages.ts
|
|
2082
|
+
//#region src/tasks/deploy-on-release/workspace-packages/workspace-packages.ts
|
|
2187
2083
|
/**
|
|
2188
2084
|
* Every package under `<repoRoot>/packages/*` and `<repoRoot>/plugins/*`
|
|
2189
2085
|
* with a readable, named `package.json` — `holocron`'s own layout splits
|
|
@@ -2349,66 +2245,182 @@ async function deployOnRelease(options, { run = defaultRun, readJson = defaultRe
|
|
|
2349
2245
|
};
|
|
2350
2246
|
}
|
|
2351
2247
|
//#endregion
|
|
2352
|
-
//#region src/
|
|
2248
|
+
//#region src/tasks/match-namespace/match-namespace.ts
|
|
2249
|
+
/** Every configured task whose name starts with `"<namespace>."`, in manifest declaration order. */
|
|
2250
|
+
function matchNamespace(config, namespace) {
|
|
2251
|
+
const prefix = `${namespace}.`;
|
|
2252
|
+
return (config.tasks ?? []).map(normalizeTaskEntry).reduce((names, entry) => {
|
|
2253
|
+
if (entry.name.startsWith(prefix) && !names.includes(entry.name)) names.push(entry.name);
|
|
2254
|
+
return names;
|
|
2255
|
+
}, []);
|
|
2256
|
+
}
|
|
2257
|
+
//#endregion
|
|
2258
|
+
//#region src/tasks/group/group.ts
|
|
2353
2259
|
/**
|
|
2354
|
-
*
|
|
2355
|
-
*
|
|
2356
|
-
*
|
|
2357
|
-
*
|
|
2358
|
-
*
|
|
2359
|
-
*
|
|
2360
|
-
* source: "packages/astromech/src/templates/configs/editorconfig/create-config.ts",
|
|
2361
|
-
* });
|
|
2362
|
-
*
|
|
2363
|
-
* export function createConfig(): string {
|
|
2364
|
-
* return `${workflowHeader()}${body}`;
|
|
2365
|
-
* }
|
|
2260
|
+
* `holocron run <namespace>` — run every configured task under one
|
|
2261
|
+
* namespace prefix (`sourceQuality.*`, `verification.*`, …) in sequence.
|
|
2262
|
+
* `Astromech.run()` falls back here whenever the given name isn't an exact
|
|
2263
|
+
* `TASKS` entry but matches the prefix of one or more configured tasks —
|
|
2264
|
+
* same "aggregate several real tasks into one report" shape `runCi` already
|
|
2265
|
+
* established, scoped to one namespace instead of the merge-gating set.
|
|
2366
2266
|
*
|
|
2367
|
-
*
|
|
2368
|
-
*
|
|
2369
|
-
*
|
|
2370
|
-
* |-------------------|----------------------------------------------------|----------------|
|
|
2371
|
-
* | `workflowHeader` | Files overwritten on every setup / sync-github run | No — "AUTO-GENERATED — do not edit" |
|
|
2372
|
-
* | `scaffoldHeader` | Files written once; user takes ownership after | Yes — "Scaffolded — edit this file freely" |
|
|
2373
|
-
*
|
|
2374
|
-
* ## `workflowHeader` formats
|
|
2375
|
-
*
|
|
2376
|
-
* | Format | Comment style | Used for |
|
|
2377
|
-
* |---------------------|------------------------|-------------------------------------------------------|
|
|
2378
|
-
* | `"yaml"` (default) | `# …` lines | YAML workflows, ignore files, `.editorconfig`, labeler|
|
|
2379
|
-
* | `"cjs"` | `/* … *\/` block | CommonJS config files (`.cjs`) |
|
|
2380
|
-
* | `"shebang"` | `#!/bin/sh` + `# …` | Shell scripts (`prepare-commit-msg`, hooks) |
|
|
2267
|
+
* Every task in the namespace always runs — one task's failure never skips
|
|
2268
|
+
* the rest, same as `runCi`'s own loop — so a single invocation surfaces
|
|
2269
|
+
* every problem in the namespace at once instead of stopping at the first.
|
|
2381
2270
|
*/
|
|
2382
|
-
function
|
|
2383
|
-
const {
|
|
2384
|
-
const
|
|
2271
|
+
function runGroup(input) {
|
|
2272
|
+
const { print, namespace } = input;
|
|
2273
|
+
const names = matchNamespace(input.config, namespace);
|
|
2274
|
+
if (names.length === 0) return {
|
|
2275
|
+
status: "unknown",
|
|
2276
|
+
jobs: []
|
|
2277
|
+
};
|
|
2278
|
+
print(`holocron run ${namespace} — ${names.length} task(s)`);
|
|
2279
|
+
print("");
|
|
2280
|
+
const jobs = [];
|
|
2281
|
+
for (const name of names) {
|
|
2282
|
+
print(`▶ ${name}`);
|
|
2283
|
+
const r = runTask({
|
|
2284
|
+
print: (line) => print(` ${line}`),
|
|
2285
|
+
logger: input.logger,
|
|
2286
|
+
exec: input.exec,
|
|
2287
|
+
readFile: input.readFile,
|
|
2288
|
+
fileExists: input.fileExists,
|
|
2289
|
+
listDir: input.listDir,
|
|
2290
|
+
lookPath: input.lookPath,
|
|
2291
|
+
task: name,
|
|
2292
|
+
cwd: input.cwd,
|
|
2293
|
+
dryRun: input.dryRun ?? false,
|
|
2294
|
+
...input.filter ? { filter: input.filter } : {}
|
|
2295
|
+
});
|
|
2296
|
+
jobs.push({
|
|
2297
|
+
task: name,
|
|
2298
|
+
status: r.status,
|
|
2299
|
+
command: r.command,
|
|
2300
|
+
message: r.message
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2385
2303
|
return {
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2304
|
+
status: jobs.some((j) => j.status === "fail") ? "fail" : jobs.every((j) => j.status === "dry-run") ? "dry-run" : "ok",
|
|
2305
|
+
jobs
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
//#endregion
|
|
2309
|
+
//#region src/astromech.ts
|
|
2310
|
+
/**
|
|
2311
|
+
* `createAstromech(options)` — the self-contained task runner.
|
|
2312
|
+
* `@theholocron/cli` instantiates it once and delegates the `run` /
|
|
2313
|
+
* `ci` / workflow-generation commands to it (like `@theholocron/observability`).
|
|
2314
|
+
*/
|
|
2315
|
+
function createAstromech(options) {
|
|
2316
|
+
const deps = {
|
|
2317
|
+
print: options.print ?? ((line) => console.log(line)),
|
|
2318
|
+
logger: options.logger ?? noopLogger,
|
|
2319
|
+
exec: options.exec ?? realExec,
|
|
2320
|
+
readFile: options.readFile ?? ((path) => readFileSync(path, "utf8")),
|
|
2321
|
+
fileExists: options.fileExists ?? ((path) => existsSync(path)),
|
|
2322
|
+
listDir: options.listDir ?? ((path) => readdirSync(path)),
|
|
2323
|
+
lookPath: options.lookPath ?? realLookPath
|
|
2324
|
+
};
|
|
2325
|
+
const items = () => (options.config?.tasks ?? []).map((i) => normalizeTaskEntry(i));
|
|
2326
|
+
/**
|
|
2327
|
+
* Task names this repo's manifest can actually run locally — shared by
|
|
2328
|
+
* {@link packageScripts} and by `holocron`'s own interactive task picker.
|
|
2329
|
+
* Deliberately independent of `syncScripts`/`holocronScript`/`hooks`:
|
|
2330
|
+
* those govern whether/how `package.json` scripts get *written*, not
|
|
2331
|
+
* which tasks exist to run. A repo with `syncScripts: false` (this repo
|
|
2332
|
+
* itself, among others) still has real runnable tasks — found live when
|
|
2333
|
+
* `packageScripts()`'s `{}` short-circuit silently emptied the picker too.
|
|
2334
|
+
*/
|
|
2335
|
+
const runnableTaskNames = () => {
|
|
2336
|
+
const out = [];
|
|
2337
|
+
for (const entry of items()) {
|
|
2338
|
+
const def = TASKS[entry.name];
|
|
2339
|
+
const hasNoLocalRunner = def?.local === null && !def.linterGroup && !def.jobs;
|
|
2340
|
+
if (entry.local === false || !KNOWN_TASKS.has(entry.name) || hasNoLocalRunner) continue;
|
|
2341
|
+
out.push(entry.name);
|
|
2411
2342
|
}
|
|
2343
|
+
return out;
|
|
2344
|
+
};
|
|
2345
|
+
return {
|
|
2346
|
+
run: (task, opts = {}) => {
|
|
2347
|
+
if (!TASKS[task] && options.config && matchNamespace(options.config, task).length > 0) return runGroup({
|
|
2348
|
+
...deps,
|
|
2349
|
+
cwd: options.cwd,
|
|
2350
|
+
config: options.config,
|
|
2351
|
+
namespace: task,
|
|
2352
|
+
dryRun: opts.dryRun ?? false,
|
|
2353
|
+
...opts.filter ? { filter: opts.filter } : {}
|
|
2354
|
+
});
|
|
2355
|
+
return runTask({
|
|
2356
|
+
...deps,
|
|
2357
|
+
task,
|
|
2358
|
+
cwd: options.cwd,
|
|
2359
|
+
...opts.job !== void 0 ? { job: opts.job } : {},
|
|
2360
|
+
passthrough: opts.passthrough ?? [],
|
|
2361
|
+
dryRun: opts.dryRun ?? false,
|
|
2362
|
+
required: opts.required ?? false,
|
|
2363
|
+
...opts.filter ? { filter: opts.filter } : {}
|
|
2364
|
+
});
|
|
2365
|
+
},
|
|
2366
|
+
ci: (opts = {}) => runCi({
|
|
2367
|
+
...deps,
|
|
2368
|
+
cwd: options.cwd,
|
|
2369
|
+
config: options.config ?? {},
|
|
2370
|
+
...opts
|
|
2371
|
+
}),
|
|
2372
|
+
thinCallers: () => {
|
|
2373
|
+
const orgCtx = options.orgContext ?? {};
|
|
2374
|
+
const out = /* @__PURE__ */ new Map();
|
|
2375
|
+
for (const entry of items()) {
|
|
2376
|
+
const isDocsSite = entry.name === "knowledge.docs";
|
|
2377
|
+
const isComponents = entry.name === "knowledge.components";
|
|
2378
|
+
if (entry.ci === false || !KNOWN_WORKFLOWS.has(entry.name) && !isDocsSite && !isComponents) continue;
|
|
2379
|
+
if (entry.name === "delivery.deploy" && entry.with?.["on"] === "release") continue;
|
|
2380
|
+
const rawWith = isDocsSite ? {
|
|
2381
|
+
preview: true,
|
|
2382
|
+
...entry.with,
|
|
2383
|
+
docs: entry.with?.["docs"] ?? true
|
|
2384
|
+
} : isComponents ? {
|
|
2385
|
+
preview: true,
|
|
2386
|
+
...entry.with,
|
|
2387
|
+
storybook: entry.with?.["storybook"] ?? [{ workingDir: "." }]
|
|
2388
|
+
} : entry.with;
|
|
2389
|
+
const withOverrides = rawWith ? normalizeWorkflowWith(rawWith) : void 0;
|
|
2390
|
+
if (entry.name === "verification.unitTests" && withOverrides?.["run-unit"] === false && withOverrides?.["run-storybook"] === false) throw new Error("test workflow: at least one of \"run-unit\" or \"run-storybook\" must be true. Library repos use run-unit: true; UI/Storybook repos use run-storybook: true.");
|
|
2391
|
+
const isDeployFamily = entry.name === "delivery.deploy" || isDocsSite || isComponents;
|
|
2392
|
+
const additionalPaths = entry.paths ?? (isDeployFamily && rawWith ? deriveDeployPaths(rawWith) : void 0);
|
|
2393
|
+
if (isDeployFamily && rawWith) {
|
|
2394
|
+
const preview = extractPreviewConfig(rawWith, orgCtx);
|
|
2395
|
+
if (preview) {
|
|
2396
|
+
const deployWith = normalizeWorkflowWith(rawWith);
|
|
2397
|
+
const deployPaths = entry.paths ?? deriveDeployPaths(rawWith);
|
|
2398
|
+
out.set(`${entry.name}.yml`, generateCombinedDeployContent(deployWith, deployPaths, preview));
|
|
2399
|
+
continue;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
out.set(`${entry.name}.yml`, generateThinCallerContent(entry.name, withOverrides, additionalPaths, deps.logger));
|
|
2403
|
+
}
|
|
2404
|
+
return out;
|
|
2405
|
+
},
|
|
2406
|
+
packageScripts: () => {
|
|
2407
|
+
const out = {};
|
|
2408
|
+
if (!options.config || options.config.syncScripts === false) return out;
|
|
2409
|
+
out.holocron = options.config.holocronScript ?? "holocron";
|
|
2410
|
+
for (const name of runnableTaskNames()) out[name] = `holocron run ${name} --`;
|
|
2411
|
+
const hooks = options.config.hooks;
|
|
2412
|
+
if (hooks === true || typeof hooks === "object" && hooks.prePush !== false) out.prepare = "husky";
|
|
2413
|
+
return out;
|
|
2414
|
+
},
|
|
2415
|
+
runnableTasks: () => runnableTaskNames(),
|
|
2416
|
+
reusableTemplates: () => reusableTemplates(),
|
|
2417
|
+
requiredChecks: () => requiredChecks(options.config ?? {}),
|
|
2418
|
+
codecovConfig: (existing) => codecovConfig(options.cwd, existing),
|
|
2419
|
+
turboConfig: () => turboConfig(options.config ?? {}),
|
|
2420
|
+
ensureRootWorkspaceMember: (workspaceYaml, rootScripts) => {
|
|
2421
|
+
return ensureRootWorkspaceMember(workspaceYaml, rootScripts, (options.config?.tasks ?? []).map(normalizeTaskEntry).filter((e) => e.local !== false).map((e) => e.name));
|
|
2422
|
+
},
|
|
2423
|
+
ensureTurboDependency: (packageJson, workspaceYaml) => ensureTurboDependency(packageJson, workspaceYaml)
|
|
2412
2424
|
};
|
|
2413
2425
|
}
|
|
2414
2426
|
//#endregion
|