-
Notifications
You must be signed in to change notification settings - Fork 0
feat: deploy-by-reference via native harper CLI (prototype / draft) #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
91da07a
feat: scaffold deploy-by-reference using the native harper CLI
dawsontoth 1f54169
fix(templates): address PR review — pin harper CLI, exclude legacy ea…
dawsontoth 8d78b9e
refactor(deploy): reconcile to sealed-token + git+https credential model
dawsontoth 47aacea
refactor(deploy): align scaffolding with token-based CI auth and Next.js
dawsontoth f1cc949
chore(templates): scaffold credential=true instead of naming the host
dawsontoth 542c344
fix(templates): generate deploy workflows for the detected package ma…
dawsontoth e248479
chore: ignore a node_modules symlink, not just a real directory
dawsontoth 6f5cb93
test: assert generated workflows in process, not through a subprocess…
dawsontoth 77f1143
fix(templates): provision Yarn Berry via Corepack in generated deploy…
dawsontoth 6f33546
fix(ci): scaffold the Yarn Berry check outside the repo, skip the CLI…
dawsontoth a9f5c8d
fix(ci): seed the Yarn Berry lockfile before the immutable install
dawsontoth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| // Indentation of the scaffolded GitHub Actions workflows (2-space YAML, so a `steps:` entry's | ||
| // `-` sits six columns in). Each multi-line value below replaces a placeholder comment that | ||
| // already sits at that indent, so a value's *first* line carries no indentation and every | ||
| // continuation line carries it explicitly. | ||
| const STEP_INDENT = ' '.repeat(6); | ||
| const KEY_INDENT = ' '.repeat(2); | ||
| const INPUT_INDENT = ' '.repeat(4); | ||
|
|
||
| /** | ||
| * Builds one `steps:` entry, or a bare comment when a package manager needs no setup step. | ||
| * | ||
| * @param {{name?: string, comment?: string[], uses?: string, inputs?: Record<string, string>, run?: string}} step | ||
| * @returns {string} - The step's YAML, indented for substitution into the workflow. | ||
| */ | ||
| function buildStep({ name, comment, uses, inputs, run }) { | ||
| const lines = []; | ||
| if (name) { lines.push(`- name: ${name}`); } | ||
| for (const line of comment ?? []) { | ||
| // A comment-only value stands in for a step, so it starts at the step indent; a comment | ||
| // documenting a step is nested with that step's other keys. | ||
| lines.push(`${name ? KEY_INDENT : ''}# ${line}`); | ||
| } | ||
| if (uses) { lines.push(`${KEY_INDENT}uses: ${uses}`); } | ||
| if (inputs) { | ||
| lines.push(`${KEY_INDENT}with:`); | ||
| for (const [input, value] of Object.entries(inputs)) { | ||
| lines.push(`${INPUT_INDENT}${input}: ${value}`); | ||
| } | ||
| } | ||
| if (run) { lines.push(`${KEY_INDENT}run: ${run}`); } | ||
| return lines.join(`\n${STEP_INDENT}`); | ||
| } | ||
|
|
||
| /** | ||
| * Extracts the major version from a package manager version string. | ||
| * | ||
| * @param {string | undefined} version - A version such as '4.9.1'. | ||
| * @returns {number | undefined} - The major version, or undefined if it can't be determined. | ||
| */ | ||
| function majorVersion(version) { | ||
| const major = Number.parseInt(version ?? '', 10); | ||
| return Number.isNaN(major) ? undefined : major; | ||
| } | ||
|
|
||
| /** | ||
| * Builds the step that puts the project's package manager on PATH, pinned to the version that | ||
| * generated its lockfile. npm and Yarn 1.x (Classic) need none — npm ships with Node.js, and | ||
| * Classic is preinstalled on GitHub's Ubuntu runners — so they get a comment saying so instead. | ||
| * | ||
| * @param {string} agent - The package manager agent ('npm', 'pnpm', 'yarn', 'bun' or 'deno'). | ||
| * @param {string} [version] - The agent's version, as reported by the user agent that invoked us. | ||
| * @returns {string} - The step's YAML. | ||
| */ | ||
| function getSetupStep(agent, version) { | ||
| switch (agent) { | ||
| case 'pnpm': | ||
| return buildStep({ | ||
| name: 'Set up pnpm', | ||
| comment: [ | ||
| "Pinned to the pnpm that wrote this project's lockfile. The action is SHA-pinned, but a", | ||
| 'floating `version:` would still let it self-install an unvetted pnpm at run time.', | ||
| ], | ||
| uses: 'pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9', | ||
| inputs: { version: version ?? 'latest' }, | ||
| }); | ||
| case 'bun': | ||
| return buildStep({ | ||
| name: 'Set up Bun', | ||
| comment: ["Pinned to the Bun that wrote this project's lockfile."], | ||
| uses: 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0', | ||
| inputs: { 'bun-version': version ?? 'latest' }, | ||
| }); | ||
| case 'deno': | ||
| return buildStep({ | ||
| name: 'Set up Deno', | ||
| comment: ["Pinned to the Deno that wrote this project's lockfile."], | ||
| uses: 'denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5', | ||
| inputs: { 'deno-version': version ?? 'vx.x.x' }, | ||
| }); | ||
| case 'yarn': | ||
| // Only Yarn 2+ (Berry) needs provisioning. The runner ships Yarn 1.x (Classic), and a | ||
| // scaffolded project pins no `packageManager`/`.yarnrc.yml`, so without this Berry's | ||
| // `--immutable` (below) would run under Classic, which rejects the flag and stops the | ||
| // deploy at install. Corepack activates the exact Yarn that wrote yarn.lock: `enable` | ||
| // installs the shim, `prepare … --activate` sets that version as the default a bare | ||
| // `yarn` resolves to (no `packageManager` field required). | ||
| if ((majorVersion(version) ?? 1) < 2) { | ||
| return buildStep({ | ||
| comment: ["Yarn 1.x (Classic) is preinstalled on GitHub's Ubuntu runners, so it needs no setup step."], | ||
| }); | ||
| } | ||
| return buildStep({ | ||
| name: `Set up Yarn ${version}`, | ||
| comment: ["Pinned to the Yarn that wrote this project's lockfile."], | ||
| run: `corepack enable && corepack prepare yarn@${version} --activate`, | ||
| }); | ||
| default: | ||
| return buildStep({ comment: ['npm ships with Node.js, so it needs no setup step.'] }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Builds `actions/setup-node`'s `cache:` input. Only npm, Yarn and pnpm are supported there; | ||
| * Bun and Deno cache through their own setup actions, so they get a comment explaining the gap | ||
| * rather than an input setup-node would reject. | ||
| * | ||
| * @param {string} agent - The package manager agent ('npm', 'pnpm', 'yarn', 'bun' or 'deno'). | ||
| * @returns {string} - The `cache:` input, or a comment. | ||
| */ | ||
| function getNodeCacheInput(agent) { | ||
| switch (agent) { | ||
| case 'bun': | ||
| return "# setup-node caches npm, Yarn and pnpm only; oven-sh/setup-bun caches Bun's store itself."; | ||
| case 'deno': | ||
| return '# setup-node caches npm, Yarn and pnpm only; denoland/setup-deno caches DENO_DIR itself.'; | ||
| case 'pnpm': | ||
| case 'yarn': | ||
| return `cache: '${agent}'`; | ||
| default: | ||
| return "cache: 'npm'"; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Gets the lockfile-respecting install command for CI, which must fail rather than update the | ||
| * lockfile when it has drifted from package.json. | ||
| * | ||
| * @param {string} agent - The package manager agent ('npm', 'pnpm', 'yarn', 'bun' or 'deno'). | ||
| * @param {string} [version] - The agent's version, as reported by the user agent that invoked us. | ||
| * @returns {string} - The install command. | ||
| */ | ||
| function getCiInstallCommand(agent, version) { | ||
| switch (agent) { | ||
| case 'pnpm': | ||
| case 'bun': | ||
| return `${agent} install --frozen-lockfile`; | ||
| case 'yarn': | ||
| // Yarn renamed the flag in 2.0; Yarn 1 rejects `--immutable` and Yarn 2+ rejects | ||
| // `--frozen-lockfile`, so pick by the version that scaffolded the project. | ||
| return (majorVersion(version) ?? 1) >= 2 ? 'yarn install --immutable' : 'yarn install --frozen-lockfile'; | ||
| case 'deno': | ||
| return 'deno install --frozen'; | ||
| default: | ||
| return 'npm ci'; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Gets the command prefix that runs a package.json script, e.g. `npm run` in `npm run deploy`. | ||
| * | ||
| * @param {string} agent - The package manager agent ('npm', 'pnpm', 'yarn', 'bun' or 'deno'). | ||
| * @returns {string} - The prefix, without a trailing space. | ||
| */ | ||
| function getRunScriptPrefix(agent) { | ||
| switch (agent) { | ||
| case 'deno': | ||
| return 'deno task'; | ||
| case 'pnpm': | ||
| case 'yarn': | ||
| case 'bun': | ||
| return `${agent} run`; | ||
| default: | ||
| return 'npm run'; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Builds the substitutions that adapt a scaffolded project's GitHub Actions workflows to the | ||
| * package manager that invoked us. Without them the workflows would hard-code npm and fail for | ||
| * everyone else: setup-node can't resolve a package lock for a project whose lockfile is | ||
| * `pnpm-lock.yaml`, and `npm ci` errors out before the job ever reaches tests or deploy. | ||
| * | ||
| * Placeholders that stand in for a whole line are written as YAML comments in the templates, so | ||
| * the committed workflows stay valid, formattable YAML; the indentation contract for their | ||
| * multi-line replacements lives in this module. | ||
| * | ||
| * @param {string} agent - The package manager agent ('npm', 'pnpm', 'yarn', 'bun' or 'deno'). | ||
| * @param {string} [version] - The agent's version, as reported by the user agent that invoked us. | ||
| * @returns {Record<string, string>} - A mapping of placeholder to replacement. | ||
| */ | ||
| export function getWorkflowSubstitutions(agent, version) { | ||
| return { | ||
| '# your-package-manager-setup-step-here': getSetupStep(agent, version), | ||
| '# your-package-manager-node-cache-here': getNodeCacheInput(agent), | ||
| 'your-package-manager-install-here': getCiInstallCommand(agent, version), | ||
| 'your-package-manager-run-here': getRunScriptPrefix(agent), | ||
| }; | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.