@theholocron/cli 4.0.1 → 4.2.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 +18 -0
- package/dist/cli.mjs +56 -259
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +2 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -75,6 +75,24 @@ Additional `repo` fields recognised by `holocron setup`:
|
|
|
75
75
|
| `repo.protection` | `"balanced" \| "strict" \| "none"` | Branch-protection preset applied by `holocron setup`. |
|
|
76
76
|
| `repo.properties` | `RepoProperties` | Org-level custom property values synced to the GitHub dashboard. |
|
|
77
77
|
|
|
78
|
+
### Task scripts
|
|
79
|
+
|
|
80
|
+
`holocron sync` reconciles `package.json#scripts` from the `tasks` manifest
|
|
81
|
+
(merge, never clobber — only the managed keys are touched):
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
export default defineConfig({
|
|
85
|
+
tasks: ["test", "lint", "typecheck"],
|
|
86
|
+
// syncScripts: false, // disable the step
|
|
87
|
+
// holocronScript: "node packages/cli/dist/cli.mjs", // override the "holocron" entry
|
|
88
|
+
providers: { source: "github" },
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Writes `"holocron": "<holocronScript ?? 'holocron'>"` plus one
|
|
93
|
+
`"<task>": "holocron run <task>"` per runnable task. `syncScripts: false`
|
|
94
|
+
opts out entirely.
|
|
95
|
+
|
|
78
96
|
### Skills installer
|
|
79
97
|
|
|
80
98
|
`holocron setup` can install shared skills from `@theholocron/skills` into the local repo:
|
package/dist/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { checkbox, input, select } from "@inquirer/prompts";
|
|
6
|
-
import { createAstromech } from "@theholocron/astromech";
|
|
6
|
+
import { KNOWN_WORKFLOWS, WORKFLOW_CHECK_CONTEXTS, WORKFLOW_TEMPLATES, createAstromech, deriveDeployPaths, extractPreviewConfig, generateCombinedDeployContent, generateThinCallerContent, normalizeWorkflowWith } from "@theholocron/astromech";
|
|
7
7
|
import yargs from "yargs";
|
|
8
8
|
import { hideBin } from "yargs/helpers";
|
|
9
9
|
import { AuthError, ProviderApiError, ProviderApiError as ProviderApiError$1, createRestClient } from "@theholocron/http-client";
|
|
@@ -280,6 +280,8 @@ function resolveConfig(raw) {
|
|
|
280
280
|
domain: raw.domain,
|
|
281
281
|
repo: raw.repo,
|
|
282
282
|
tasks: raw.tasks,
|
|
283
|
+
syncScripts: raw.syncScripts,
|
|
284
|
+
holocronScript: raw.holocronScript,
|
|
283
285
|
providers,
|
|
284
286
|
apps: raw.apps ?? [],
|
|
285
287
|
doctor: raw.doctor ?? {},
|
|
@@ -3494,262 +3496,6 @@ var dependabot_default = "version: 2\nupdates:\n - package-ecosystem: npm\n
|
|
|
3494
3496
|
//#region src/templates/labeler.yml
|
|
3495
3497
|
var labeler_default = "bug:\n - '^fix'\n\nchore:\n - '^chore(?!\\(deps)'\n\nci:\n - '^ci'\n\ndependencies:\n - '^chore\\(deps'\n\ndocumentation:\n - '^docs'\n\nenhancement:\n - '^feat'\n\nperformance:\n - '^perf'\n\nrefactor:\n - '^refactor'\n\ntest:\n - '^test'\n";
|
|
3496
3498
|
//#endregion
|
|
3497
|
-
//#region src/commands/setup-workflows/index.ts
|
|
3498
|
-
/**
|
|
3499
|
-
* Thin workflow wrapper templates for `holocron setup`.
|
|
3500
|
-
*
|
|
3501
|
-
* Each entry is a complete `.github/workflows/<name>.yml` that delegates
|
|
3502
|
-
* to the corresponding reusable `ci-<name>.yml` in `theholocron/.github`.
|
|
3503
|
-
* Files are overwritten on each setup run — they are generated artifacts.
|
|
3504
|
-
*/
|
|
3505
|
-
const WORKFLOW_TEMPLATES = {
|
|
3506
|
-
lint: "name: Lint\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: lint-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: write\n issues: write\n statuses: write\n\njobs:\n lint:\n name: Lint\n uses: theholocron/.github/.github/workflows/lint.yml@main\n secrets: inherit\n",
|
|
3507
|
-
test: "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 id-token: write\n statuses: write\n\njobs:\n test:\n name: Test\n uses: theholocron/.github/.github/workflows/test.yml@main\n with:\n run-unit: true\n secrets: inherit\n",
|
|
3508
|
-
typecheck: "name: Typecheck\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\nconcurrency:\n group: typecheck-${{ github.ref }}\n cancel-in-progress: true\n\npermissions:\n contents: read\n\njobs:\n typecheck:\n name: Typecheck\n uses: theholocron/.github/.github/workflows/typecheck.yml@main\n secrets: inherit\n",
|
|
3509
|
-
security: "name: Security\n\non: # yamllint disable-line rule:truthy\n push:\n branches:\n - main\n pull_request:\n branches:\n - main\n schedule:\n - cron: \"0 0 * * 1\"\n\npermissions:\n actions: read\n contents: read\n security-events: write\n\njobs:\n security:\n uses: theholocron/.github/.github/workflows/security.yml@main\n secrets: inherit\n",
|
|
3510
|
-
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",
|
|
3511
|
-
review: "name: Review\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: Review\n uses: theholocron/.github/.github/workflows/review.yml@main\n secrets: inherit\n",
|
|
3512
|
-
release: "name: Release\n\non: # yamllint disable-line rule:truthy\n push:\n branches:\n - main\n - alpha\n workflow_dispatch:\n inputs:\n dry_run:\n description: >\n Dry run — analyze commits and preview the release without git writes\n or publish. Push-triggered runs always run fully; this only applies\n to manual workflow_dispatch triggers.\n required: false\n default: true\n type: boolean\n\npermissions:\n contents: write\n id-token: write\n issues: write\n pull-requests: write\n\nconcurrency:\n group: ${{ github.workflow }}-${{ github.ref }}\n cancel-in-progress: false\n\njobs:\n release:\n uses: theholocron/.github/.github/workflows/release.yml@main\n with:\n dry-run: ${{ inputs.dry_run == true }}\n secrets: inherit\n",
|
|
3513
|
-
stale: "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 with:\n exempt-issue-labels: \"in-progress,wip\"\n exempt-all-issue-milestones: true\n exempt-all-issue-projects: true\n exempt-all-pr-projects: true\n secrets: inherit\n",
|
|
3514
|
-
sync: "name: Sync\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/sync.yml@main\n with:\n steps: ${{ inputs.steps }}\n secrets: inherit\n",
|
|
3515
|
-
greetings: "name: Greetings\n\non: # yamllint disable-line rule:truthy\n pull_request:\n issues:\n\npermissions:\n issues: write\n pull-requests: write\n\njobs:\n greetings:\n uses: theholocron/.github/.github/workflows/greetings.yml@main\n secrets: inherit\n",
|
|
3516
|
-
dependencies: "name: Dependencies\n\non: # yamllint disable-line rule:truthy\n pull_request:\n\npermissions:\n contents: write\n pull-requests: write\n\njobs:\n dependencies:\n uses: theholocron/.github/.github/workflows/dependencies.yml@main\n secrets: inherit\n",
|
|
3517
|
-
bookkeeping: "name: Bookkeeping\n\non: # yamllint disable-line rule:truthy\n pull_request:\n types:\n - opened\n - edited\n\npermissions:\n contents: read\n issues: write\n pull-requests: write\n\njobs:\n bookkeeping:\n uses: theholocron/.github/.github/workflows/bookkeeping.yml@main\n secrets: inherit\n",
|
|
3518
|
-
audit: "name: Audit\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main, alpha]\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n audit:\n uses: theholocron/.github/.github/workflows/audit.yml@main\n secrets: inherit\n",
|
|
3519
|
-
deploy: "name: Deploy\n\non: # yamllint disable-line rule:truthy\n push:\n branches: [main]\n workflow_dispatch:\n\nconcurrency:\n group: pages\n cancel-in-progress: false\n\npermissions:\n contents: read\n pages: write\n id-token: write\n\njobs:\n deploy:\n name: Deploy\n uses: theholocron/.github/.github/workflows/deploy.yml@main\n secrets: inherit\n",
|
|
3520
|
-
wiki: "name: Wiki\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/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/wiki.yml@main\n with:\n preview: true\n preview-id: pr-${{ github.event.pull_request.number }}\n secrets: inherit\n"
|
|
3521
|
-
};
|
|
3522
|
-
const KNOWN_WORKFLOWS = new Set(Object.keys(WORKFLOW_TEMPLATES));
|
|
3523
|
-
/**
|
|
3524
|
-
* GitHub check context name each CI workflow produces on a PR.
|
|
3525
|
-
*
|
|
3526
|
-
* The format is "{caller-workflow-name} / {reusable-job-name}". The caller
|
|
3527
|
-
* job's own `name:` field does NOT appear in the external check name — only
|
|
3528
|
-
* the calling workflow's top-level `name:` and the inner reusable-workflow
|
|
3529
|
-
* job name matter. Only workflows that gate merges are listed here.
|
|
3530
|
-
*/
|
|
3531
|
-
const WORKFLOW_CHECK_CONTEXTS = {
|
|
3532
|
-
lint: "Lint / Lint entire codebase",
|
|
3533
|
-
test: "Test / Run tests and collect coverage",
|
|
3534
|
-
typecheck: "Typecheck / tsc --noEmit"
|
|
3535
|
-
};
|
|
3536
|
-
/**
|
|
3537
|
-
* Generate the thin caller content for a workflow, optionally injecting or
|
|
3538
|
-
* merging `with:` overrides into the jobs block.
|
|
3539
|
-
*
|
|
3540
|
-
* Two strategies are used depending on the template:
|
|
3541
|
-
* - Templates that already have a `with:` block (e.g. lint):
|
|
3542
|
-
* the override entries are merged in, replacing existing keys and appending
|
|
3543
|
-
* new ones.
|
|
3544
|
-
* - Templates that end with ` secrets: inherit`: a new `with:` block is
|
|
3545
|
-
* injected immediately before `secrets: inherit`.
|
|
3546
|
-
* If neither pattern matches the template, a warning is emitted and the
|
|
3547
|
-
* base template is returned unchanged.
|
|
3548
|
-
*/
|
|
3549
|
-
function generateThinCallerContent(name, withOverrides, additionalPaths) {
|
|
3550
|
-
const base = WORKFLOW_TEMPLATES[name];
|
|
3551
|
-
if (!base) return "";
|
|
3552
|
-
const yamlScalar = (v) => {
|
|
3553
|
-
if (v === true) return "true";
|
|
3554
|
-
if (v === false) return "false";
|
|
3555
|
-
const s = String(v);
|
|
3556
|
-
return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
|
|
3557
|
-
};
|
|
3558
|
-
const fmt = (k, v) => ` ${k}: ${yamlScalar(v)}`;
|
|
3559
|
-
let result = base;
|
|
3560
|
-
if (additionalPaths && additionalPaths.length > 0) {
|
|
3561
|
-
const pathsBlockRe = /( {4}paths:\n)((?:[ ]{6}- [^\n]+\n)+)/;
|
|
3562
|
-
if (pathsBlockRe.test(result)) result = result.replace(pathsBlockRe, (_, header, existing) => {
|
|
3563
|
-
const existingPaths = new Set([...existing.matchAll(/- (.+)/g)].map((m) => m[1]));
|
|
3564
|
-
const newEntries = additionalPaths.filter((p) => !existingPaths.has(p)).map((p) => ` - ${p}\n`).join("");
|
|
3565
|
-
return header + existing + newEntries;
|
|
3566
|
-
});
|
|
3567
|
-
else {
|
|
3568
|
-
const pathsBlock = ` paths:\n${additionalPaths.map((p) => ` - ${p}\n`).join("")}`;
|
|
3569
|
-
result = result.replace(/( {4}branches: \[main\]\n)/, `$1${pathsBlock}`);
|
|
3570
|
-
}
|
|
3571
|
-
}
|
|
3572
|
-
if (!withOverrides || Object.keys(withOverrides).length === 0) return result;
|
|
3573
|
-
const withBlockRe = /( {4}with:\n)((?:[ ]{6}[^\n]+\n)*)/;
|
|
3574
|
-
const existingMatch = result.match(withBlockRe);
|
|
3575
|
-
if (existingMatch) {
|
|
3576
|
-
const existingEntries = new Map(existingMatch[2].split("\n").filter(Boolean).map((line) => {
|
|
3577
|
-
const m = line.match(/^ {6}([^:]+):\s*(.*)/);
|
|
3578
|
-
return m ? [m[1].trim(), m[2].trim()] : null;
|
|
3579
|
-
}).filter((e) => e !== null));
|
|
3580
|
-
for (const [k, v] of Object.entries(withOverrides)) existingEntries.set(k, yamlScalar(v));
|
|
3581
|
-
const merged = [...existingEntries.entries()].map(([k, v]) => ` ${k}: ${v}`).join("\n");
|
|
3582
|
-
return result.replace(withBlockRe, ` with:\n${merged}\n`);
|
|
3583
|
-
}
|
|
3584
|
-
const withBlock = Object.entries(withOverrides).map(([k, v]) => fmt(k, v)).join("\n");
|
|
3585
|
-
const injected = result.replace(/ {4}secrets: inherit\n$/, ` with:\n${withBlock}\n secrets: inherit\n`);
|
|
3586
|
-
if (injected === result) getLogger().warn({ template: name }, "generateThinCallerContent: could not inject `with:` overrides");
|
|
3587
|
-
return injected;
|
|
3588
|
-
}
|
|
3589
|
-
/**
|
|
3590
|
-
* Extract the Cloudflare Pages preview config from a deploy workflow's `with:` object.
|
|
3591
|
-
*
|
|
3592
|
-
* Accepts three forms:
|
|
3593
|
-
* - `preview: true` — derive both project and domain from org context
|
|
3594
|
-
* - `preview: { project: "..." }` — explicit project; domain derived from context if omitted
|
|
3595
|
-
* - `preview: { project: "...", domain: "..." }` — fully explicit
|
|
3596
|
-
*
|
|
3597
|
-
* Returns null when `preview:` is absent, false, or can't be resolved.
|
|
3598
|
-
*/
|
|
3599
|
-
function extractPreviewConfig(raw, ctx = {}) {
|
|
3600
|
-
const preview = raw["preview"];
|
|
3601
|
-
if (!preview) return null;
|
|
3602
|
-
if (preview === true) {
|
|
3603
|
-
const project = ctx.org ? `${ctx.org}-preview` : null;
|
|
3604
|
-
const domain = ctx.domain ? `preview.${ctx.domain}` : void 0;
|
|
3605
|
-
if (!project) return null;
|
|
3606
|
-
return {
|
|
3607
|
-
project,
|
|
3608
|
-
...domain ? { domain } : {}
|
|
3609
|
-
};
|
|
3610
|
-
}
|
|
3611
|
-
if (typeof preview !== "object") return null;
|
|
3612
|
-
const p = preview;
|
|
3613
|
-
const project = typeof p["project"] === "string" && p["project"] ? p["project"] : ctx.org ? `${ctx.org}-preview` : null;
|
|
3614
|
-
if (!project) return null;
|
|
3615
|
-
const domain = typeof p["domain"] === "string" && p["domain"] ? p["domain"] : ctx.domain ? `preview.${ctx.domain}` : void 0;
|
|
3616
|
-
return {
|
|
3617
|
-
project,
|
|
3618
|
-
...domain ? { domain } : {}
|
|
3619
|
-
};
|
|
3620
|
-
}
|
|
3621
|
-
/**
|
|
3622
|
-
* Generate the full thin-caller YAML for a `deploy.yml` that handles both
|
|
3623
|
-
* production (push to main → GitHub Pages) and preview (pull_request →
|
|
3624
|
-
* Cloudflare Pages) in a single file.
|
|
3625
|
-
*
|
|
3626
|
-
* Both jobs receive the same docs/storybook `with:` inputs. If the per-repo
|
|
3627
|
-
* config supplies `cloudflare-project` it is forwarded; otherwise the reusable
|
|
3628
|
-
* falls back to the `CLOUDFLARE_PAGES_PROJECT` org variable — set that once and
|
|
3629
|
-
* all repos with a `deploy` workflow get previews without per-repo config.
|
|
3630
|
-
*/
|
|
3631
|
-
function generateCombinedDeployContent(deployWith, paths, preview) {
|
|
3632
|
-
const yamlScalar = (v) => {
|
|
3633
|
-
if (v === true) return "true";
|
|
3634
|
-
if (v === false) return "false";
|
|
3635
|
-
const s = String(v);
|
|
3636
|
-
return s.startsWith("[") || s.startsWith("{") ? `'${s}'` : s;
|
|
3637
|
-
};
|
|
3638
|
-
const withLines = (entries) => Object.entries(entries).map(([k, v]) => ` ${k}: ${yamlScalar(v)}`).join("\n");
|
|
3639
|
-
const pathsBlock = paths.length > 0 ? ` paths:\n${paths.map((p) => ` - ${p}\n`).join("")}` : "";
|
|
3640
|
-
const previewWith = {
|
|
3641
|
-
...deployWith,
|
|
3642
|
-
"cloudflare-project": preview.project
|
|
3643
|
-
};
|
|
3644
|
-
const deployWithBlock = Object.keys(deployWith).length > 0 ? ` with:\n${withLines(deployWith)}\n` : "";
|
|
3645
|
-
const previewWithBlock = ` with:\n${withLines(previewWith)}\n`;
|
|
3646
|
-
return [
|
|
3647
|
-
`name: Deploy`,
|
|
3648
|
-
``,
|
|
3649
|
-
`on: # yamllint disable-line rule:truthy`,
|
|
3650
|
-
` push:`,
|
|
3651
|
-
` branches: [main]`,
|
|
3652
|
-
...pathsBlock ? [`${pathsBlock}`] : [],
|
|
3653
|
-
` pull_request:`,
|
|
3654
|
-
` branches: [main]`,
|
|
3655
|
-
` types: [opened, synchronize, reopened, closed]`,
|
|
3656
|
-
...pathsBlock ? [`${pathsBlock}`] : [],
|
|
3657
|
-
` workflow_dispatch:`,
|
|
3658
|
-
``,
|
|
3659
|
-
`concurrency:`,
|
|
3660
|
-
` group: $\{{ github.event_name == 'pull_request' && format('preview-{0}', github.event.pull_request.number) || 'pages' }}`,
|
|
3661
|
-
` cancel-in-progress: $\{{ github.event_name == 'pull_request' && github.event.action != 'closed' }}`,
|
|
3662
|
-
``,
|
|
3663
|
-
`permissions:`,
|
|
3664
|
-
` contents: read`,
|
|
3665
|
-
` deployments: write`,
|
|
3666
|
-
` pages: write`,
|
|
3667
|
-
` id-token: write`,
|
|
3668
|
-
` pull-requests: write`,
|
|
3669
|
-
``,
|
|
3670
|
-
`jobs:`,
|
|
3671
|
-
` deploy:`,
|
|
3672
|
-
` name: Deploy`,
|
|
3673
|
-
` if: \${{ github.event_name != 'pull_request' }}`,
|
|
3674
|
-
` uses: theholocron/.github/.github/workflows/deploy.yml@main`,
|
|
3675
|
-
...deployWithBlock ? [deployWithBlock.trimEnd()] : [],
|
|
3676
|
-
` secrets: inherit`,
|
|
3677
|
-
``,
|
|
3678
|
-
` preview:`,
|
|
3679
|
-
` name: Preview`,
|
|
3680
|
-
` if: \${{ github.event_name == 'pull_request' }}`,
|
|
3681
|
-
` uses: theholocron/.github/.github/workflows/preview.yml@main`,
|
|
3682
|
-
previewWithBlock.trimEnd(),
|
|
3683
|
-
` secrets: inherit`,
|
|
3684
|
-
``
|
|
3685
|
-
].join("\n");
|
|
3686
|
-
}
|
|
3687
|
-
/**
|
|
3688
|
-
* Expand structured with-values to flat GitHub Actions inputs before
|
|
3689
|
-
* generating the thin caller. Handles:
|
|
3690
|
-
* - deploy shorthand: docs/storybook → type + storybook-projects
|
|
3691
|
-
* - preview: stripped (handled separately via extractPreviewConfig)
|
|
3692
|
-
* - run-chromatic object → run-chromatic: true + chromatic-projects
|
|
3693
|
-
* - plain arrays → JSON-stringified for YAML scalar quoting
|
|
3694
|
-
*
|
|
3695
|
-
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
3696
|
-
*/
|
|
3697
|
-
function normalizeWorkflowWith(raw) {
|
|
3698
|
-
const result = { ...raw };
|
|
3699
|
-
delete result["preview"];
|
|
3700
|
-
const hasDocs = raw["docs"] === true || raw["docs"] !== null && typeof raw["docs"] === "object";
|
|
3701
|
-
const storybookProjects = raw["storybook"];
|
|
3702
|
-
if (hasDocs) {
|
|
3703
|
-
result["type"] = "docs";
|
|
3704
|
-
delete result["docs"];
|
|
3705
|
-
}
|
|
3706
|
-
if (Array.isArray(storybookProjects)) {
|
|
3707
|
-
if (!hasDocs) result["type"] = "storybook";
|
|
3708
|
-
result["storybook-projects"] = JSON.stringify(storybookProjects.map(({ name, path = "." }) => ({
|
|
3709
|
-
name,
|
|
3710
|
-
workingDir: path
|
|
3711
|
-
})));
|
|
3712
|
-
delete result["storybook"];
|
|
3713
|
-
}
|
|
3714
|
-
const runChromatic = raw["run-chromatic"];
|
|
3715
|
-
if (runChromatic !== null && typeof runChromatic === "object" && "projects" in runChromatic) {
|
|
3716
|
-
result["run-chromatic"] = true;
|
|
3717
|
-
const projects = runChromatic.projects.map((p) => ({
|
|
3718
|
-
...p,
|
|
3719
|
-
...Array.isArray(p.untraced) ? { untraced: p.untraced.join("\n") } : {}
|
|
3720
|
-
}));
|
|
3721
|
-
result["chromatic-projects"] = JSON.stringify(projects);
|
|
3722
|
-
}
|
|
3723
|
-
for (const [k, v] of Object.entries(result)) if (Array.isArray(v)) result[k] = JSON.stringify(v);
|
|
3724
|
-
return result;
|
|
3725
|
-
}
|
|
3726
|
-
/**
|
|
3727
|
-
* Derive on.push.paths entries from the deploy with: shorthand.
|
|
3728
|
-
* Used by both `holocron setup` and `sync-workflow-templates`.
|
|
3729
|
-
*/
|
|
3730
|
-
function deriveDeployPaths(raw) {
|
|
3731
|
-
const paths = [];
|
|
3732
|
-
const docs = raw["docs"];
|
|
3733
|
-
if (docs === true) {
|
|
3734
|
-
paths.push("docs/**");
|
|
3735
|
-
paths.push("astro.config.ts");
|
|
3736
|
-
paths.push("pnpm-workspace.yaml");
|
|
3737
|
-
paths.push("pnpm-lock.yaml");
|
|
3738
|
-
} else if (docs !== null && typeof docs === "object" && "path" in docs) {
|
|
3739
|
-
const p = docs.path;
|
|
3740
|
-
if (p && p !== ".") paths.push(`${p}/**`);
|
|
3741
|
-
}
|
|
3742
|
-
const storybookProjects = raw["storybook"];
|
|
3743
|
-
if (Array.isArray(storybookProjects)) for (const s of storybookProjects) {
|
|
3744
|
-
const p = s.path || ".";
|
|
3745
|
-
if (p === ".") {
|
|
3746
|
-
paths.push("src/**");
|
|
3747
|
-
paths.push(".storybook/**");
|
|
3748
|
-
} else paths.push(`${p}/**`);
|
|
3749
|
-
}
|
|
3750
|
-
return paths;
|
|
3751
|
-
}
|
|
3752
|
-
//#endregion
|
|
3753
3499
|
//#region src/commands/setup/agent-prompts-data.ts
|
|
3754
3500
|
/**
|
|
3755
3501
|
* Canonical AI engineering workflow role prompts.
|
|
@@ -5580,6 +5326,7 @@ const SYNC_STEPS = [
|
|
|
5580
5326
|
"homepage",
|
|
5581
5327
|
"readme",
|
|
5582
5328
|
"workflows",
|
|
5329
|
+
"scripts",
|
|
5583
5330
|
"wiki"
|
|
5584
5331
|
];
|
|
5585
5332
|
const LOCAL_STEPS = /* @__PURE__ */ new Set([
|
|
@@ -5588,6 +5335,7 @@ const LOCAL_STEPS = /* @__PURE__ */ new Set([
|
|
|
5588
5335
|
"homepage",
|
|
5589
5336
|
"readme",
|
|
5590
5337
|
"workflows",
|
|
5338
|
+
"scripts",
|
|
5591
5339
|
"wiki"
|
|
5592
5340
|
]);
|
|
5593
5341
|
async function runSync(input) {
|
|
@@ -5735,6 +5483,7 @@ async function runSync(input) {
|
|
|
5735
5483
|
"homepage",
|
|
5736
5484
|
"readme",
|
|
5737
5485
|
"workflows",
|
|
5486
|
+
"scripts",
|
|
5738
5487
|
"wiki"
|
|
5739
5488
|
]) {
|
|
5740
5489
|
if (requestedSteps !== void 0 && !requestedSteps.includes(stepName)) continue;
|
|
@@ -5867,6 +5616,29 @@ async function runSync(input) {
|
|
|
5867
5616
|
print(formatSyncStep(steps[steps.length - 1]));
|
|
5868
5617
|
}
|
|
5869
5618
|
}
|
|
5619
|
+
if (stepName === "scripts") if (config.syncScripts === false) {
|
|
5620
|
+
steps.push({
|
|
5621
|
+
capability: "local",
|
|
5622
|
+
step: "sync scripts",
|
|
5623
|
+
status: "skip",
|
|
5624
|
+
message: "syncScripts: false"
|
|
5625
|
+
});
|
|
5626
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
5627
|
+
} else {
|
|
5628
|
+
const tasksConfig = { tasks: config.tasks };
|
|
5629
|
+
if (config.holocronScript !== void 0) tasksConfig.holocronScript = config.holocronScript;
|
|
5630
|
+
const desired = createAstromech({
|
|
5631
|
+
cwd: input.context.repoRoot,
|
|
5632
|
+
config: tasksConfig
|
|
5633
|
+
}).packageScripts();
|
|
5634
|
+
steps.push(await runSyncStep("local", "sync scripts", dryRun, async () => {
|
|
5635
|
+
const changed = await mergePackageJsonScripts(input.context.repoRoot, desired);
|
|
5636
|
+
if (changed === null) return "no package.json";
|
|
5637
|
+
if (changed.length === 0) return `${Object.keys(desired).length} scripts already current`;
|
|
5638
|
+
return `${changed.join(", ")} set`;
|
|
5639
|
+
}));
|
|
5640
|
+
print(formatSyncStep(steps[steps.length - 1]));
|
|
5641
|
+
}
|
|
5870
5642
|
if (stepName === "wiki") {
|
|
5871
5643
|
const result = await runSyncWiki({
|
|
5872
5644
|
loaded: input.loaded,
|
|
@@ -5944,6 +5716,31 @@ async function writePackageJsonField(repoRoot, field, value) {
|
|
|
5944
5716
|
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5945
5717
|
return true;
|
|
5946
5718
|
}
|
|
5719
|
+
/**
|
|
5720
|
+
* Merge `desired` script entries into `package.json#scripts` — sets only the
|
|
5721
|
+
* listed keys, never touches the rest. Returns the sorted list of keys that
|
|
5722
|
+
* drifted (empty when all current), or `null` when there is no `package.json`.
|
|
5723
|
+
*/
|
|
5724
|
+
async function mergePackageJsonScripts(repoRoot, desired) {
|
|
5725
|
+
const pkgPath = join(repoRoot, "package.json");
|
|
5726
|
+
let content;
|
|
5727
|
+
try {
|
|
5728
|
+
content = await readFile(pkgPath, "utf8");
|
|
5729
|
+
} catch {
|
|
5730
|
+
return null;
|
|
5731
|
+
}
|
|
5732
|
+
const pkg = JSON.parse(content);
|
|
5733
|
+
const scripts = pkg.scripts ?? {};
|
|
5734
|
+
const changed = [];
|
|
5735
|
+
for (const [name, command] of Object.entries(desired)) if (scripts[name] !== command) {
|
|
5736
|
+
scripts[name] = command;
|
|
5737
|
+
changed.push(name);
|
|
5738
|
+
}
|
|
5739
|
+
if (changed.length === 0) return [];
|
|
5740
|
+
pkg.scripts = scripts;
|
|
5741
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5742
|
+
return changed.sort();
|
|
5743
|
+
}
|
|
5947
5744
|
const README_DESC_START = "<!-- holocron:description -->";
|
|
5948
5745
|
const README_DESC_END = "<!-- /holocron:description -->";
|
|
5949
5746
|
async function updateReadmeDescription(repoRoot, description) {
|
|
@@ -7206,10 +7003,10 @@ try {
|
|
|
7206
7003
|
dryRun: argv.dryRun,
|
|
7207
7004
|
...argv.otp ? { otp: argv.otp } : {}
|
|
7208
7005
|
})).status === "fail") process.exitCode = 1;
|
|
7209
|
-
}).demandCommand(1, "Run `holocron npm --help` to see available npm subcommands."), () => {}).command("sync [steps..]", "Sync state from config to the provider and local files (labels, properties, topics, keywords, description, homepage, readme, workflows)", (y) => y.positional("steps", {
|
|
7006
|
+
}).demandCommand(1, "Run `holocron npm --help` to see available npm subcommands."), () => {}).command("sync [steps..]", "Sync state from config to the provider and local files (labels, properties, teams, topics, keywords, description, homepage, readme, workflows, scripts, wiki)", (y) => y.positional("steps", {
|
|
7210
7007
|
type: "string",
|
|
7211
7008
|
array: true,
|
|
7212
|
-
describe: "Steps to run: labels, properties, topics, keywords, description, homepage, readme, workflows (default: all)"
|
|
7009
|
+
describe: "Steps to run: labels, properties, teams, topics, keywords, description, homepage, readme, workflows, scripts, wiki (default: all)"
|
|
7213
7010
|
}).option("repo", {
|
|
7214
7011
|
type: "string",
|
|
7215
7012
|
describe: "Repo coords (\"owner/name\"). Defaults to plugin-specific resolution."
|