diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 71146b4..57ca498 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -180,3 +180,68 @@ jobs: fi echo "Integration test passed for ${{ matrix.pkg-manager }} with ${{ matrix.template }}!" + + # The matrix above exercises Yarn 1.x (Classic). Yarn 2+ (Berry) is a different animal: it isn't + # on the runner, and a scaffolded project pins no packageManager/.yarnrc.yml, so the generated + # deploy workflow provisions it with Corepack (lib/pkg/getWorkflowSubstitutions.js). This job runs + # that generated setup + install path for real, in the workflow's own ordering — Corepack + # activation with the runner's default Node *before* setup-node swaps it — to prove the activated + # Yarn survives the Node swap and that `yarn install --immutable` (which Classic would reject) then + # succeeds. deployWorkflow.test.js asserts the generator still emits exactly these commands. + yarn-berry-install: + name: Yarn Berry deploy-install path + runs-on: ubuntu-latest + env: + YARN_VERSION: 4.9.1 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + # Runs against the runner's default Node, before the pinned Node below — exactly as the + # generated workflow's "Set up Yarn" step precedes its "Set up Node.js" step. + - name: Activate Yarn Berry with Corepack + run: corepack enable && corepack prepare "yarn@$YARN_VERSION" --activate + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run the generated Yarn Berry setup + install path + env: + CREATE_HARPER_SKIP_UPDATE: 'true' + run: | + set -euo pipefail + REPO="$(pwd)" + # Scaffold outside the repo: Berry looks upward for its project root, and create-harper's + # own package.json would otherwise capture the new project as a stray workspace. + PROJECT_DIR="$(mktemp -d)/berry-app" + mkdir -p "$(dirname "$PROJECT_DIR")" + + ( cd "$(dirname "$PROJECT_DIR")" \ + && npm_config_user_agent="yarn/$YARN_VERSION npm/? node/$(node -v) linux x64" \ + node "$REPO/index.js" berry-app --template vanilla --no-interactive --overwrite --skip-install ) + + workflow="$PROJECT_DIR/.github/workflows/deploy.yaml" + echo "Asserting the generated workflow drives Yarn through Corepack, not the runner's Yarn 1..." + grep -q "corepack prepare yarn@$YARN_VERSION --activate" "$workflow" + grep -q 'yarn install --immutable' "$workflow" + + cd "$PROJECT_DIR" + echo "yarn is $(yarn --version) at $(command -v yarn)" + + # Stand in for the yarn.lock the user commits: Berry auto-enables immutable installs on a + # public PR runner, which forbids creating a lockfile, so disable it for this one seeding + # install. (A fresh scaffold ships no lockfile; the deploy workflow runs against a repo + # that already has one.) + YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install + + # The reviewer's failure mode: `yarn install --immutable` under the runner's preinstalled + # Yarn 1 errors on the unknown flag. That `yarn is 4.9.1` line above already shows the + # Corepack activation survived setup-node's Node swap; this proves the generated command + # then succeeds under Berry. + yarn install --immutable diff --git a/.gitignore b/.gitignore index bb9bc11..3e6609e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ .DS_Store .temp-integration-tests +# Matches a `node_modules` *symlink* too — git worktrees commonly link to the main checkout's +# install, and the `node_modules/` entry in the vendored Node.gitignore below has a trailing +# slash, so it only ever matches a real directory. +node_modules + # Playwright e2e artifacts /test-results/ /playwright-report/ diff --git a/lib/init.js b/lib/init.js index 549a271..0918d78 100644 --- a/lib/init.js +++ b/lib/init.js @@ -76,7 +76,7 @@ export async function init() { // Write out the contents based on all prior steps. const cwd = process.cwd(); const root = path.join(cwd, targetDir); - scaffoldProject(root, projectName, packageName, template, envVars, pkgManager); + scaffoldProject(root, projectName, packageName, template, envVars, pkgManager, pkgInfo?.version); // Log out the next steps. installAndOptionallyStart(root, pkgManager, immediate, args.skipInstall, selectedSkills, selectedAgents); diff --git a/lib/init.test.js b/lib/init.test.js index cb228d1..03e7cb8 100644 --- a/lib/init.test.js +++ b/lib/init.test.js @@ -124,6 +124,8 @@ describe('init.js', () => { 'vanilla', { target: 't' }, expect.any(String), + // The package manager's version, which pins it in the scaffolded CI workflows. + expect.any(String), ); expect(installAndOptionallyStart).toHaveBeenCalledWith( expect.stringContaining('my-dir'), diff --git a/lib/pkg/getWorkflowSubstitutions.js b/lib/pkg/getWorkflowSubstitutions.js new file mode 100644 index 0000000..8e760f4 --- /dev/null +++ b/lib/pkg/getWorkflowSubstitutions.js @@ -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, 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} - 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), + }; +} diff --git a/lib/pkg/getWorkflowSubstitutions.test.js b/lib/pkg/getWorkflowSubstitutions.test.js new file mode 100644 index 0000000..29b7dab --- /dev/null +++ b/lib/pkg/getWorkflowSubstitutions.test.js @@ -0,0 +1,144 @@ +import { describe, expect, test } from 'vitest'; +import { getWorkflowSubstitutions } from './getWorkflowSubstitutions.js'; + +const SETUP_STEP = '# your-package-manager-setup-step-here'; +const NODE_CACHE = '# your-package-manager-node-cache-here'; +const INSTALL = 'your-package-manager-install-here'; +const RUN = 'your-package-manager-run-here'; + +describe(getWorkflowSubstitutions, () => { + test('substitutes every placeholder the scaffolded workflows carry', () => { + expect(Object.keys(getWorkflowSubstitutions('npm', '10.9.0'))).toEqual([ + SETUP_STEP, + NODE_CACHE, + INSTALL, + RUN, + ]); + }); + + describe('npm', () => { + test('needs no setup step, and caches and installs through npm', () => { + const substitutions = getWorkflowSubstitutions('npm', '10.9.0'); + + expect(substitutions[SETUP_STEP]).toBe('# npm ships with Node.js, so it needs no setup step.'); + expect(substitutions[NODE_CACHE]).toBe("cache: 'npm'"); + expect(substitutions[INSTALL]).toBe('npm ci'); + expect(substitutions[RUN]).toBe('npm run'); + }); + }); + + describe('pnpm', () => { + test('sets pnpm up at the detected version, SHA-pinning the action', () => { + const substitutions = getWorkflowSubstitutions('pnpm', '11.17.0'); + + // The step is substituted into a comment that already sits at the workflow's step + // indent, so the first line is bare and the rest carry their indentation. + expect(substitutions[SETUP_STEP]).toBe( + `- name: Set up pnpm + # 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 + with: + version: 11.17.0`, + ); + expect(substitutions[NODE_CACHE]).toBe("cache: 'pnpm'"); + expect(substitutions[INSTALL]).toBe('pnpm install --frozen-lockfile'); + expect(substitutions[RUN]).toBe('pnpm run'); + }); + + test('falls back to the latest pnpm when the user agent carried no version', () => { + expect(getWorkflowSubstitutions('pnpm')[SETUP_STEP]).toContain('version: latest'); + }); + }); + + describe('yarn', () => { + test('1.x (Classic) is preinstalled, so no setup step, and uses its lockfile flag', () => { + const substitutions = getWorkflowSubstitutions('yarn', '1.22.22'); + + expect(substitutions[SETUP_STEP]).toBe( + "# Yarn 1.x (Classic) is preinstalled on GitHub's Ubuntu runners, so it needs no setup step.", + ); + // No provisioning, so nothing to reject `--frozen-lockfile`. + expect(substitutions[SETUP_STEP]).not.toContain('corepack'); + expect(substitutions[NODE_CACHE]).toBe("cache: 'yarn'"); + expect(substitutions[INSTALL]).toBe('yarn install --frozen-lockfile'); + expect(substitutions[RUN]).toBe('yarn run'); + }); + + test('2+ (Berry) is provisioned via Corepack, pinned to the detected version', () => { + const substitutions = getWorkflowSubstitutions('yarn', '4.9.1'); + + // Berry isn't on the runner; without this step the preinstalled Yarn 1 would run the + // `--immutable` install below and reject the flag. `--activate` makes a bare `yarn` + // resolve to this version with no `packageManager`/`.yarnrc.yml` in the project. + expect(substitutions[SETUP_STEP]).toBe( + `- name: Set up Yarn 4.9.1 + # Pinned to the Yarn that wrote this project's lockfile. + run: corepack enable && corepack prepare yarn@4.9.1 --activate`, + ); + expect(substitutions[INSTALL]).toBe('yarn install --immutable'); + expect(substitutions[RUN]).toBe('yarn run'); + }); + + test('every 2+ line gets the Corepack step, since 2.0 dropped --frozen-lockfile', () => { + for (const version of ['2.4.3', '3.8.7', '4.9.1']) { + const substitutions = getWorkflowSubstitutions('yarn', version); + expect(substitutions[SETUP_STEP]).toContain(`corepack prepare yarn@${version} --activate`); + expect(substitutions[INSTALL]).toBe('yarn install --immutable'); + } + }); + + test('assumes Classic when the user agent carried no usable version, so it never emits a bare `corepack prepare yarn@`', () => { + for (const substitutions of [getWorkflowSubstitutions('yarn'), getWorkflowSubstitutions('yarn', 'stable')]) { + expect(substitutions[SETUP_STEP]).not.toContain('corepack'); + expect(substitutions[INSTALL]).toBe('yarn install --frozen-lockfile'); + } + }); + }); + + describe('bun', () => { + test('sets Bun up at the detected version and leaves caching to setup-bun', () => { + const substitutions = getWorkflowSubstitutions('bun', '1.2.19'); + + expect(substitutions[SETUP_STEP]).toBe( + `- name: Set up Bun + # Pinned to the Bun that wrote this project's lockfile. + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.2.19`, + ); + // setup-node rejects any cache value other than npm, Yarn or pnpm, so this has to be a + // comment rather than an input. + expect(substitutions[NODE_CACHE]).toMatch(/^#/); + expect(substitutions[NODE_CACHE]).toContain('oven-sh/setup-bun'); + expect(substitutions[INSTALL]).toBe('bun install --frozen-lockfile'); + expect(substitutions[RUN]).toBe('bun run'); + }); + }); + + describe('deno', () => { + test('sets Deno up at the detected version and runs scripts as tasks', () => { + const substitutions = getWorkflowSubstitutions('deno', '2.5.0'); + + expect(substitutions[SETUP_STEP]).toBe( + `- name: Set up Deno + # Pinned to the Deno that wrote this project's lockfile. + uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5 + with: + deno-version: 2.5.0`, + ); + expect(substitutions[NODE_CACHE]).toMatch(/^#/); + expect(substitutions[NODE_CACHE]).toContain('denoland/setup-deno'); + expect(substitutions[INSTALL]).toBe('deno install --frozen'); + expect(substitutions[RUN]).toBe('deno task'); + }); + }); + + test('falls back to npm for an agent with no CI story, so the job fails loudly', () => { + const substitutions = getWorkflowSubstitutions('unknown', '1.0.0'); + + expect(substitutions[NODE_CACHE]).toBe("cache: 'npm'"); + expect(substitutions[INSTALL]).toBe('npm ci'); + expect(substitutions[RUN]).toBe('npm run'); + }); +}); diff --git a/lib/steps/scaffoldProject.js b/lib/steps/scaffoldProject.js index 88b1663..6ddd26a 100644 --- a/lib/steps/scaffoldProject.js +++ b/lib/steps/scaffoldProject.js @@ -3,6 +3,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { crawlTemplateDir } from '../fs/crawlTemplateDir.js'; +import { getWorkflowSubstitutions } from '../pkg/getWorkflowSubstitutions.js'; /** * Step 6: Create the project structure and files based on the collected information. @@ -13,16 +14,22 @@ import { crawlTemplateDir } from '../fs/crawlTemplateDir.js'; * @param {string} template - The template name to use. * @param {import('./getEnvVars.js').EnvVars} [envVars] - Environment variables to substitute. * @param {string} [pkgManager] - The package manager that invoked us (e.g. npm, pnpm, yarn, bun). Defaults to npm. + * @param {string} [pkgManagerVersion] - That package manager's version, used to pin it in CI. */ -export function scaffoldProject(root, projectName, packageName, template, envVars, pkgManager) { +export function scaffoldProject(root, projectName, packageName, template, envVars, pkgManager, pkgManagerVersion) { fs.mkdirSync(root, { recursive: true }); prompts.log.step(`Scaffolding project in ${root}...`); + const agent = pkgManager || 'npm'; const substitutions = { 'your-project-name-here': projectName || 'your-project-name-here', 'your-package-name-here': packageName || 'your-package-name-here', 'your-fabric.harper.fast-cluster-url-here': envVars?.target || 'your-fabric.harper.fast-cluster-url-here', - 'your-package-manager-here': pkgManager || 'npm', + // Substitution runs these keys in order as plain string replaces, so the longer + // `your-package-manager-*-here` placeholders resolve before the bare one below and can + // never have their prefix eaten by it. + ...getWorkflowSubstitutions(agent, pkgManagerVersion), + 'your-package-manager-here': agent, '\n\t"repository": "github:HarperFast/create-harper",': '', }; diff --git a/lib/steps/scaffoldProject.test.js b/lib/steps/scaffoldProject.test.js index c986f48..d550ee6 100644 --- a/lib/steps/scaffoldProject.test.js +++ b/lib/steps/scaffoldProject.test.js @@ -22,7 +22,7 @@ describe('scaffoldProject', () => { target: 'testtarget', }; - scaffoldProject(root, projectName, packageName, template, envVars, 'pnpm'); + scaffoldProject(root, projectName, packageName, template, envVars, 'pnpm', '11.17.0'); expect(fs.mkdirSync).toHaveBeenCalledWith(expect.any(String), { recursive: true }); expect(prompts.log.step).toHaveBeenCalledWith(expect.stringContaining('Scaffolding project')); @@ -34,6 +34,9 @@ describe('scaffoldProject', () => { 'your-package-name-here': packageName, 'your-fabric.harper.fast-cluster-url-here': envVars.target, 'your-package-manager-here': 'pnpm', + // The CI workflows follow the detected package manager rather than hard-coding npm. + 'your-package-manager-install-here': 'pnpm install --frozen-lockfile', + 'your-package-manager-run-here': 'pnpm run', }), ); }); @@ -51,6 +54,8 @@ describe('scaffoldProject', () => { 'your-package-name-here': 'your-package-name-here', 'your-fabric.harper.fast-cluster-url-here': 'your-fabric.harper.fast-cluster-url-here', 'your-package-manager-here': 'npm', + 'your-package-manager-install-here': 'npm ci', + 'your-package-manager-run-here': 'npm run', }), ); }); diff --git a/template-nextjs-ts/README.md b/template-nextjs-ts/README.md index 6f96d52..a0c6d14 100644 --- a/template-nextjs-ts/README.md +++ b/template-nextjs-ts/README.md @@ -19,7 +19,7 @@ npm install -g harper Start the app: ```sh -npm run dev +your-package-manager-run-here dev ``` Then open [http://localhost:9926](http://localhost:9926) 🎉 @@ -67,10 +67,34 @@ harper login Then deploy your app: ```sh -npm run deploy +your-package-manager-run-here deploy ``` -`npm run deploy` runs `next build` locally and ships the prebuilt `.next` output, then Harper serves it — no build runs on the cluster. (Building on the cluster currently fails; see the note in [`config.yaml`](./config.yaml).) +`your-package-manager-run-here deploy` runs `next build` locally and ships the prebuilt `.next` output, then Harper serves it — no build runs on the cluster. (Building on the cluster currently fails; see the note in [`config.yaml`](./config.yaml).) + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) builds and deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +> **Why this template deploys differently.** The other create-harper templates deploy _by reference_: the cluster clones your repo at a pinned commit and builds there. Next.js can't do that yet — `.next` is gitignored, so a git reference carries no build output, and an on-cluster build currently fails ([nextjs#57](https://github.com/HarperFast/nextjs/issues/57), [nextjs#58](https://github.com/HarperFast/nextjs/issues/58)). Until those land, this template uploads the build itself. ## Keep Going! diff --git a/template-nextjs-ts/_github/workflow/deploy.yaml b/template-nextjs-ts/_github/workflow/deploy.yaml deleted file mode 100644 index 2dc4dc5..0000000 --- a/template-nextjs-ts/_github/workflow/deploy.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-nextjs-ts/_github/workflows/deploy.yaml b/template-nextjs-ts/_github/workflows/deploy.yaml new file mode 100644 index 0000000..b4e352a --- /dev/null +++ b/template-nextjs-ts/_github/workflows/deploy.yaml @@ -0,0 +1,62 @@ +# Deploys this app to your Harper cluster by *payload* via `harper deploy_component .` +# (`your-package-manager-run-here deploy`), which runs `next build` first and uploads the prebuilt +# `.next` output. +# +# The other create-harper templates deploy by *reference* (`harper deploy by_ref=true`), where the +# cluster clones your repo and builds on the node. Next.js can't use that yet: `.next` is gitignored +# (so a git reference carries no build output) and building on the cluster currently fails — +# Turbopack crashes inside a Harper worker thread (HarperFast/nextjs#57) and a webpack build +# overruns the component-load timeout (HarperFast/nextjs#58). Once those land, this template can +# switch to the by-reference workflow like the rest. +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line; ^5.2.0 blocks a + # breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Build & deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-nextjs/README.md b/template-nextjs/README.md index f274945..873ab97 100644 --- a/template-nextjs/README.md +++ b/template-nextjs/README.md @@ -19,7 +19,7 @@ npm install -g harper Start the app: ```sh -npm run dev +your-package-manager-run-here dev ``` Then open [http://localhost:9926](http://localhost:9926) 🎉 @@ -67,10 +67,34 @@ harper login Then deploy your app: ```sh -npm run deploy +your-package-manager-run-here deploy ``` -`npm run deploy` runs `next build` locally and ships the prebuilt `.next` output, then Harper serves it — no build runs on the cluster. (Building on the cluster currently fails; see the note in [`config.yaml`](./config.yaml).) +`your-package-manager-run-here deploy` runs `next build` locally and ships the prebuilt `.next` output, then Harper serves it — no build runs on the cluster. (Building on the cluster currently fails; see the note in [`config.yaml`](./config.yaml).) + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) builds and deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +> **Why this template deploys differently.** The other create-harper templates deploy _by reference_: the cluster clones your repo at a pinned commit and builds there. Next.js can't do that yet — `.next` is gitignored, so a git reference carries no build output, and an on-cluster build currently fails ([nextjs#57](https://github.com/HarperFast/nextjs/issues/57), [nextjs#58](https://github.com/HarperFast/nextjs/issues/58)). Until those land, this template uploads the build itself. ## Keep Going! diff --git a/template-nextjs/_github/workflow/deploy.yaml b/template-nextjs/_github/workflow/deploy.yaml deleted file mode 100644 index 2dc4dc5..0000000 --- a/template-nextjs/_github/workflow/deploy.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-nextjs/_github/workflows/deploy.yaml b/template-nextjs/_github/workflows/deploy.yaml new file mode 100644 index 0000000..b4e352a --- /dev/null +++ b/template-nextjs/_github/workflows/deploy.yaml @@ -0,0 +1,62 @@ +# Deploys this app to your Harper cluster by *payload* via `harper deploy_component .` +# (`your-package-manager-run-here deploy`), which runs `next build` first and uploads the prebuilt +# `.next` output. +# +# The other create-harper templates deploy by *reference* (`harper deploy by_ref=true`), where the +# cluster clones your repo and builds on the node. Next.js can't use that yet: `.next` is gitignored +# (so a git reference carries no build output) and building on the cluster currently fails — +# Turbopack crashes inside a Harper worker thread (HarperFast/nextjs#57) and a webpack build +# overruns the component-load timeout (HarperFast/nextjs#58). Once those land, this template can +# switch to the by-reference workflow like the rest. +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line; ^5.2.0 blocks a + # breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Build & deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-react-ssr/README.md b/template-react-ssr/README.md index f31ffcd..6d8029d 100644 --- a/template-react-ssr/README.md +++ b/template-react-ssr/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` ### Define Your Schema @@ -103,20 +103,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-react-ssr/_github/workflow/deploy.yaml b/template-react-ssr/_github/workflow/deploy.yaml deleted file mode 100644 index cc5039e..0000000 --- a/template-react-ssr/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-react-ssr/_github/workflows/deploy.yaml b/template-react-ssr/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-react-ssr/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-react-ssr/package.json b/template-react-ssr/package.json index 86066fd..d705813 100644 --- a/template-react-ssr/package.json +++ b/template-react-ssr/package.json @@ -13,7 +13,8 @@ "test": "node --test test/*.test.js", "test:watch": "node --watch --test test/*.test.js", "build": "vite build", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "dependencies": { "@harperfast/schema-codegen": "^2.0.0", diff --git a/template-react-ts-ssr/README.md b/template-react-ts-ssr/README.md index 90bc495..ba52829 100644 --- a/template-react-ts-ssr/README.md +++ b/template-react-ts-ssr/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` TypeScript is supported at runtime in Node.js through [type stripping](https://nodejs.org/api/typescript.html#type-stripping). Full TypeScript language support can be enabled through integrating third party build steps to transpile your TypeScript into JavaScript. @@ -111,20 +111,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-react-ts-ssr/_github/workflow/deploy.yaml b/template-react-ts-ssr/_github/workflow/deploy.yaml deleted file mode 100644 index cc5039e..0000000 --- a/template-react-ts-ssr/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-react-ts-ssr/_github/workflows/deploy.yaml b/template-react-ts-ssr/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-react-ts-ssr/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-react-ts-ssr/package.json b/template-react-ts-ssr/package.json index a6d1bfd..463708e 100644 --- a/template-react-ts-ssr/package.json +++ b/template-react-ts-ssr/package.json @@ -13,7 +13,8 @@ "test": "node --test test/*.test.ts", "test:watch": "node --watch --test test/*.test.ts", "build": "vite build", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "dependencies": { "@harperfast/schema-codegen": "^2.0.0", diff --git a/template-react-ts/README.md b/template-react-ts/README.md index 90bc495..ba52829 100644 --- a/template-react-ts/README.md +++ b/template-react-ts/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` TypeScript is supported at runtime in Node.js through [type stripping](https://nodejs.org/api/typescript.html#type-stripping). Full TypeScript language support can be enabled through integrating third party build steps to transpile your TypeScript into JavaScript. @@ -111,20 +111,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-react-ts/_github/workflow/deploy.yaml b/template-react-ts/_github/workflow/deploy.yaml deleted file mode 100644 index cc5039e..0000000 --- a/template-react-ts/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-react-ts/_github/workflows/deploy.yaml b/template-react-ts/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-react-ts/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-react-ts/package.json b/template-react-ts/package.json index a6d1bfd..463708e 100644 --- a/template-react-ts/package.json +++ b/template-react-ts/package.json @@ -13,7 +13,8 @@ "test": "node --test test/*.test.ts", "test:watch": "node --watch --test test/*.test.ts", "build": "vite build", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "dependencies": { "@harperfast/schema-codegen": "^2.0.0", diff --git a/template-react/README.md b/template-react/README.md index f31ffcd..6d8029d 100644 --- a/template-react/README.md +++ b/template-react/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` ### Define Your Schema @@ -103,20 +103,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-react/_github/workflow/deploy.yaml b/template-react/_github/workflow/deploy.yaml deleted file mode 100644 index cc5039e..0000000 --- a/template-react/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-react/_github/workflows/deploy.yaml b/template-react/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-react/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-react/package.json b/template-react/package.json index 86066fd..d705813 100644 --- a/template-react/package.json +++ b/template-react/package.json @@ -13,7 +13,8 @@ "test": "node --test test/*.test.js", "test:watch": "node --watch --test test/*.test.js", "build": "vite build", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "dependencies": { "@harperfast/schema-codegen": "^2.0.0", diff --git a/template-vanilla-ts/README.md b/template-vanilla-ts/README.md index 7ff7891..68b396b 100644 --- a/template-vanilla-ts/README.md +++ b/template-vanilla-ts/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` TypeScript is supported at runtime in Node.js through [type stripping](https://nodejs.org/api/typescript.html#type-stripping). Full TypeScript language support can be enabled through integrating third party build steps to transpile your TypeScript into JavaScript. @@ -111,20 +111,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-vanilla-ts/_github/workflow/deploy.yaml b/template-vanilla-ts/_github/workflow/deploy.yaml deleted file mode 100644 index e3ee65c..0000000 --- a/template-vanilla-ts/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Deploy - run: npm run deploy diff --git a/template-vanilla-ts/_github/workflows/deploy.yaml b/template-vanilla-ts/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-vanilla-ts/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-vanilla-ts/package.json b/template-vanilla-ts/package.json index 463b97b..769c149 100644 --- a/template-vanilla-ts/package.json +++ b/template-vanilla-ts/package.json @@ -12,7 +12,8 @@ "format": "prettier --write .", "test": "node --test test/*.test.js", "test:watch": "node --watch --test test/*.test.js", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/template-vanilla/README.md b/template-vanilla/README.md index 3e6ec4f..71fd0df 100644 --- a/template-vanilla/README.md +++ b/template-vanilla/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` ### Define Your Schema @@ -103,20 +103,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-vanilla/_github/workflow/deploy.yaml b/template-vanilla/_github/workflow/deploy.yaml deleted file mode 100644 index e3ee65c..0000000 --- a/template-vanilla/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Deploy - run: npm run deploy diff --git a/template-vanilla/_github/workflows/deploy.yaml b/template-vanilla/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-vanilla/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-vanilla/package.json b/template-vanilla/package.json index 463b97b..769c149 100644 --- a/template-vanilla/package.json +++ b/template-vanilla/package.json @@ -12,7 +12,8 @@ "format": "prettier --write .", "test": "node --test test/*.test.js", "test:watch": "node --watch --test test/*.test.js", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/template-vue-ssr/README.md b/template-vue-ssr/README.md index f31ffcd..6d8029d 100644 --- a/template-vue-ssr/README.md +++ b/template-vue-ssr/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` ### Define Your Schema @@ -103,20 +103,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-vue-ssr/_github/workflow/deploy.yaml b/template-vue-ssr/_github/workflow/deploy.yaml deleted file mode 100644 index cc5039e..0000000 --- a/template-vue-ssr/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-vue-ssr/_github/workflows/deploy.yaml b/template-vue-ssr/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-vue-ssr/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-vue-ssr/package.json b/template-vue-ssr/package.json index 092be7f..791dfca 100644 --- a/template-vue-ssr/package.json +++ b/template-vue-ssr/package.json @@ -13,7 +13,8 @@ "test": "node --test test/*.test.js", "test:watch": "node --watch --test test/*.test.js", "build": "vite build", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "dependencies": { "@harperfast/schema-codegen": "^2.0.0", diff --git a/template-vue-ts-ssr/README.md b/template-vue-ts-ssr/README.md index 90bc495..ba52829 100644 --- a/template-vue-ts-ssr/README.md +++ b/template-vue-ts-ssr/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` TypeScript is supported at runtime in Node.js through [type stripping](https://nodejs.org/api/typescript.html#type-stripping). Full TypeScript language support can be enabled through integrating third party build steps to transpile your TypeScript into JavaScript. @@ -111,20 +111,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-vue-ts-ssr/_github/workflow/deploy.yaml b/template-vue-ts-ssr/_github/workflow/deploy.yaml deleted file mode 100644 index cc5039e..0000000 --- a/template-vue-ts-ssr/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-vue-ts-ssr/_github/workflows/deploy.yaml b/template-vue-ts-ssr/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-vue-ts-ssr/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-vue-ts-ssr/package.json b/template-vue-ts-ssr/package.json index ba5246f..c9b95f8 100644 --- a/template-vue-ts-ssr/package.json +++ b/template-vue-ts-ssr/package.json @@ -13,7 +13,8 @@ "test": "node --test test/*.test.ts", "test:watch": "node --watch --test test/*.test.ts", "build": "vite build", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "dependencies": { "@harperfast/schema-codegen": "^2.0.0", diff --git a/template-vue-ts/README.md b/template-vue-ts/README.md index 90bc495..ba52829 100644 --- a/template-vue-ts/README.md +++ b/template-vue-ts/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` TypeScript is supported at runtime in Node.js through [type stripping](https://nodejs.org/api/typescript.html#type-stripping). Full TypeScript language support can be enabled through integrating third party build steps to transpile your TypeScript into JavaScript. @@ -111,20 +111,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-vue-ts/_github/workflow/deploy.yaml b/template-vue-ts/_github/workflow/deploy.yaml deleted file mode 100644 index cc5039e..0000000 --- a/template-vue-ts/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-vue-ts/_github/workflows/deploy.yaml b/template-vue-ts/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-vue-ts/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-vue-ts/package.json b/template-vue-ts/package.json index ba5246f..c9b95f8 100644 --- a/template-vue-ts/package.json +++ b/template-vue-ts/package.json @@ -13,7 +13,8 @@ "test": "node --test test/*.test.ts", "test:watch": "node --watch --test test/*.test.ts", "build": "vite build", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "dependencies": { "@harperfast/schema-codegen": "^2.0.0", diff --git a/template-vue/README.md b/template-vue/README.md index f31ffcd..6d8029d 100644 --- a/template-vue/README.md +++ b/template-vue/README.md @@ -17,7 +17,7 @@ npm install -g harper Then you can start your app: ```sh -npm run dev +your-package-manager-run-here dev ``` ### Define Your Schema @@ -103,20 +103,62 @@ Take a look at the [default configuration](./config.yaml), which specifies how f ## Deployment -When you are ready, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in to your account, and create a cluster. +Deploy your app to a Harper cluster **by reference**: instead of uploading a snapshot of your files, you tell Harper which commit of your GitHub repository to run, pinned by its exact commit SHA. Re-deploying the same commit is repeatable, and rolling back is just deploying an older commit. -Come back and log in your local CLI to your cluster: +First, head to [https://fabric.harper.fast/](https://fabric.harper.fast/), log in, and create a cluster. Then log your local CLI in to it: ```sh harper login ``` -Then you can deploy your app to your cluster: +### One-time setup (private repos) + +So the cluster can clone your private repository, give it a read-only token — sealed on your machine, stored encrypted: + +```sh +your-package-manager-run-here deploy:setup +``` + +This fetches your cluster's public key, has you provide a GitHub token (a fine-grained PAT with **Contents: Read-only**, or your `gh` CLI session), **encrypts it locally**, and stores only the ciphertext in the cluster's secret store. The plaintext never leaves your machine; the cluster decrypts it in memory only while cloning. Because the token is durable, rollbacks keep working for as long as it's valid. + +> Public repo? Skip this step and drop `credential=true` from the `deploy` script — no credential is needed. + +### Deploy ```sh -npm run deploy +your-package-manager-run-here deploy ``` +This deploys the current commit over `git+https` — commit and push first, since the cluster clones from GitHub and only sees pushed commits. To roll back, check out an older commit and run it again. + +### Deploy automatically from CI + +The included [GitHub Actions workflow](./.github/workflows/deploy.yaml) deploys whenever you push a version tag: + +```sh +git tag v1.0.0 +git push --tags +``` + +Add these repository secrets first, under **Settings → Secrets and variables → Actions**: + +- `HARPER_CLI_TARGET` — your cluster's operations URL (e.g. `https://your-cluster.harperdb.io:9925`) +- `HARPER_CLI_REFRESH_TOKEN` — a long-lived token CI authenticates with, so no password is stored + +Set both in one command — this pipes the credentials straight from your cluster into GitHub, so the token never appears on screen or in your shell history: + +```sh +harper login --for-ci | gh secret set --env-file - +``` + +(No [`gh` CLI](https://cli.github.com)? `harper login --for-ci | pbcopy` copies the two lines for you to paste in by hand.) + +The clone credential already lives in the cluster from `your-package-manager-run-here deploy:setup`, so CI never handles a token itself. + +### Private npm dependencies + +If your app depends on private npm packages, run `your-package-manager-run-here deploy:setup` again and choose the npm registry — the same sealed-token flow, stored as a separate credential. + ## Keep Going! For more information about getting started with Harper and building applications, see our [getting started guide](https://docs.harperdb.io/docs). diff --git a/template-vue/_github/workflow/deploy.yaml b/template-vue/_github/workflow/deploy.yaml deleted file mode 100644 index cc5039e..0000000 --- a/template-vue/_github/workflow/deploy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -name: Deploy to Harper Fabric -on: - workflow_dispatch: -# push: -# branches: -# - main - -concurrency: - group: main - cancel-in-progress: false - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - fetch-tags: true - - name: Set up Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - cache: 'npm' - node-version-file: '.nvmrc' - - name: Install dependencies - run: npm ci - - name: Run unit tests - run: npm test - - name: Run lint - run: npm run lint - - name: Build & deploy - run: npm run deploy diff --git a/template-vue/_github/workflows/deploy.yaml b/template-vue/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/template-vue/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/template-vue/package.json b/template-vue/package.json index 092be7f..791dfca 100644 --- a/template-vue/package.json +++ b/template-vue/package.json @@ -13,7 +13,8 @@ "test": "node --test test/*.test.js", "test:watch": "node --watch --test test/*.test.js", "build": "vite build", - "deploy": "harper deploy_component . restart=true replicated=true" + "deploy": "harper deploy by_ref=true credential=true restart=true replicated=true", + "deploy:setup": "harper deploy setup=true" }, "dependencies": { "@harperfast/schema-codegen": "^2.0.0", diff --git a/template.tests/deployWorkflow.test.js b/template.tests/deployWorkflow.test.js new file mode 100644 index 0000000..7ebe737 --- /dev/null +++ b/template.tests/deployWorkflow.test.js @@ -0,0 +1,231 @@ +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from 'vitest'; +import { scaffoldProject } from '../lib/steps/scaffoldProject.js'; + +// Scaffolding narrates every file it writes; the assertions below care about the files, not the +// narration. The CLI runs out of process, so this only quiets the in-process calls. +vi.mock('@clack/prompts'); + +const root = path.resolve(import.meta.dirname, '..'); +const cliPath = path.resolve(root, 'index.js'); +const workflowPath = path.join('_github', 'workflows', 'deploy.yaml'); + +// Every template that ships a deploy workflow. template-early-hints deliberately ships none — it +// is a legacy `harperdb` EdgeWorker example, excluded from the deploy migration. +const templateDirs = fs.readdirSync(root) + .filter((name) => name.startsWith('template-') && fs.existsSync(path.join(root, name, workflowPath))); + +/** + * The deploy workflows must follow the package manager that scaffolded the project. create-harper + * installs with whichever one invoked it, so a project created by `pnpm create harper` commits + * `pnpm-lock.yaml` — against which setup-node's npm cache cannot resolve a package lock and + * `npm ci` fails, killing the job before it ever reaches tests or deploy. + */ +describe('deploy workflows are package-manager agnostic', () => { + test('finds template deploy workflows to check', () => { + expect(templateDirs.length).toBeGreaterThan(0); + }); + + for (const dir of templateDirs) { + test(`${dir} hard-codes no package manager`, () => { + const workflow = fs.readFileSync(path.join(root, dir, workflowPath), 'utf-8'); + + expect(workflow).toContain('# your-package-manager-setup-step-here'); + expect(workflow).toContain('# your-package-manager-node-cache-here'); + expect(workflow).toContain('run: your-package-manager-install-here'); + + expect(workflow).not.toContain('npm ci'); + expect(workflow).not.toMatch(/cache: '/); + // `npm install -g harper` stays npm on purpose — it is a global tool, and npm always + // comes with the Node.js the workflow sets up. Running a package.json script must not. + expect(workflow).not.toContain('npm run'); + expect(workflow).not.toContain('npm test'); + }); + } +}); + +describe('generated deploy workflows', () => { + /** @type {string} */ + let tempDir; + + /** + * Scaffolds a template for the given package manager and reads back the deploy workflow. + * + * @param {string} template - The template name to scaffold. + * @param {string} agent - The package manager to scaffold for. + * @param {string} [version] - That package manager's version. + * @returns {string} - The generated workflow's contents. + */ + function scaffoldFor(template, agent, version) { + const target = path.join(tempDir, `${template}-${agent}`); + scaffoldProject(target, 'test-project', 'test-project', template, undefined, agent, version); + return fs.readFileSync(path.join(target, '.github', 'workflows', 'deploy.yaml'), 'utf-8'); + } + + beforeAll(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'create-harper-deploy-workflow-')); + vi.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterAll(() => { + vi.restoreAllMocks(); + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + test('pnpm gets a pinned pnpm setup, a pnpm cache, and pnpm commands', () => { + const workflow = scaffoldFor('vanilla', 'pnpm', '11.17.0'); + + expect(workflow).toContain('- name: Set up pnpm'); + expect(workflow).toContain('uses: pnpm/action-setup@'); + expect(workflow).toContain('version: 11.17.0'); + expect(workflow).toContain("cache: 'pnpm'"); + expect(workflow).toContain('run: pnpm install --frozen-lockfile'); + expect(workflow).toContain('run: pnpm run test'); + expect(workflow).toContain('run: pnpm run deploy'); + + expect(workflow).not.toContain('npm ci'); + expect(workflow).not.toContain("cache: 'npm'"); + // The Harper CLI is the one deliberate npm holdout. + expect(workflow).toContain('run: npm install -g harper@'); + + // No placeholder may survive into a scaffolded project. + expect(workflow).not.toContain('your-package-manager'); + }); + + test('bun gets a Bun setup and no setup-node cache, which supports npm/Yarn/pnpm only', () => { + const workflow = scaffoldFor('vanilla', 'bun', '1.2.19'); + + expect(workflow).toContain('- name: Set up Bun'); + expect(workflow).toContain('uses: oven-sh/setup-bun@'); + expect(workflow).toContain('bun-version: 1.2.19'); + expect(workflow).toContain('run: bun install --frozen-lockfile'); + expect(workflow).toContain('run: bun run deploy'); + + expect(workflow).not.toMatch(/cache: '/); + expect(workflow).not.toContain('your-package-manager'); + }); + + test('deno runs package.json scripts as tasks', () => { + const workflow = scaffoldFor('vanilla', 'deno', '2.5.0'); + + expect(workflow).toContain('- name: Set up Deno'); + expect(workflow).toContain('run: deno install --frozen'); + expect(workflow).toContain('run: deno task deploy'); + expect(workflow).not.toContain('your-package-manager'); + }); + + test('Yarn 2+ (Berry) provisions the detected Yarn via Corepack before installing immutably', () => { + const workflow = scaffoldFor('vanilla', 'yarn', '4.9.1'); + + // Without the Corepack step, `yarn install --immutable` runs under the runner's preinstalled + // Yarn 1, which rejects `--immutable`. The verify-yarn-berry-install job in integration.yaml + // runs this exact pair of commands on a real runner to prove they work together. + expect(workflow).toContain('run: corepack enable && corepack prepare yarn@4.9.1 --activate'); + expect(workflow).toContain('run: yarn install --immutable'); + expect(workflow).not.toContain('--frozen-lockfile'); + expect(workflow).not.toContain('your-package-manager'); + }); + + test('Yarn 1 (Classic) is preinstalled, so no Corepack step and the classic lockfile flag', () => { + const workflow = scaffoldFor('vanilla', 'yarn', '1.22.22'); + + expect(workflow).not.toContain('corepack'); + expect(workflow).toContain('run: yarn install --frozen-lockfile'); + expect(workflow).not.toContain('your-package-manager'); + }); + + test('npm still gets the npm workflow', () => { + const workflow = scaffoldFor('vanilla', 'npm', '10.9.0'); + + expect(workflow).toContain("cache: 'npm'"); + expect(workflow).toContain('run: npm ci'); + expect(workflow).toContain('run: npm run deploy'); + expect(workflow).not.toContain('your-package-manager'); + }); + + // The Next.js templates keep their own payload-deploy workflow instead of the shared + // by-reference one, so it needs the same treatment rather than inheriting it from the fan-out. + test('the standalone Next.js payload workflow follows the package manager too', () => { + const workflow = scaffoldFor('nextjs', 'pnpm', '11.17.0'); + + expect(workflow).toContain('harper deploy_component'); + expect(workflow).toContain('- name: Set up pnpm'); + expect(workflow).toContain("cache: 'pnpm'"); + expect(workflow).toContain('run: pnpm install --frozen-lockfile'); + expect(workflow).toContain('run: pnpm run deploy'); + + expect(workflow).not.toContain('npm ci'); + expect(workflow).not.toContain('your-package-manager'); + }); +}); + +describe('the invoking package manager reaches the generated workflow', () => { + /** @type {string} */ + let tempDir; + + beforeAll(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'create-harper-user-agent-')); + }); + + afterEach(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + fs.mkdirSync(tempDir, { recursive: true }); + }); + + afterAll(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + // End to end through the CLI, so the `npm_config_user_agent` plumbing is covered too and not + // just the substitution it feeds. + test('`pnpm create harper` produces a pnpm workflow', () => { + const projectName = 'test-user-agent'; + + // Windows matches environment variable names case-insensitively, so the + // `npm_config_user_agent` npm set when it ran this test suite can survive alongside the one + // set here and win. Drop every casing of it before setting ours. + const env = Object.fromEntries( + Object.entries(process.env).filter(([key]) => key.toLowerCase() !== 'npm_config_user_agent'), + ); + + const result = spawnSync('node', [ + cliPath, + projectName, + '--template', + 'vanilla', + '--no-interactive', + '--overwrite', + ], { + cwd: tempDir, + env: { + ...env, + _HARPER_TEST_CLI: '1', + CREATE_HARPER_SKIP_UPDATE: '1', + npm_config_user_agent: 'pnpm/11.17.0 npm/? node/v22.0.0 linux x64', + }, + encoding: 'utf-8', + }); + + if (result.status !== 0) { + console.error(result.stderr); + console.log(result.stdout); + } + expect(result.status).toBe(0); + + // Checked first, and against the CLI's own account of what it detected, so a failure here + // separates "the user agent never reached the child" from "the workflow came out wrong". + expect(result.stdout, `CLI output was:\n${result.stdout}`).toContain('dependencies with pnpm'); + + const workflow = fs.readFileSync( + path.join(tempDir, projectName, '.github', 'workflows', 'deploy.yaml'), + 'utf-8', + ); + + expect(workflow).toContain('run: pnpm install --frozen-lockfile'); + expect(workflow).toContain('version: 11.17.0'); + expect(workflow).not.toContain('npm ci'); + }); +}); diff --git a/template.tests/template.test.js b/template.tests/template.test.js index 0bc2504..40deb9e 100644 --- a/template.tests/template.test.js +++ b/template.tests/template.test.js @@ -8,6 +8,11 @@ const root = path.resolve(import.meta.dirname, '..'); const cliPath = path.resolve(root, 'index.js'); const tempDir = path.resolve(root, '.temp-integration-tests'); +// Templates that still deploy by payload rather than by reference. Kept as an explicit list so +// adding a template can't silently opt out of deploy-by-reference — a new one fails the by-ref +// assertions below until it's either wired up or added here with a reason. +const PAYLOAD_DEPLOY_TEMPLATES = new Set(['nextjs', 'nextjs-ts']); + describe('Integration tests', () => { beforeAll(() => { if (fs.existsSync(tempDir)) { @@ -69,15 +74,35 @@ describe('Integration tests', () => { const templateDir = path.resolve(root, `template-${template}`); if (fs.existsSync(path.join(templateDir, '_env'))) { expect(fs.existsSync(path.join(targetDir, '.env'))).toBe(true); + // Credentials come from `harper login` (local) or GitHub Actions secrets (CI); the + // scaffolded .env only selects the target cluster. const envContent = fs.readFileSync(path.join(targetDir, '.env'), 'utf-8'); - expect(envContent).toContain('CLI_TARGET_USERNAME'); - expect(envContent).toContain('CLI_TARGET_PASSWORD'); expect(envContent).toContain('CLI_TARGET'); + expect(envContent).not.toContain('CLI_TARGET_USERNAME'); + expect(envContent).not.toContain('CLI_TARGET_PASSWORD'); } if (fs.existsSync(path.join(templateDir, '_env.example'))) { expect(fs.existsSync(path.join(targetDir, '.env.example'))).toBe(true); } + + // The deploy workflow must live under `.github/workflows/` (plural — GitHub only runs + // workflows there; the singular `workflow/` these templates used to ship never triggered). + expect(fs.existsSync(path.join(targetDir, '.github', 'workflows', 'deploy.yaml'))).toBe(true); + // Deploy is driven by the native harper CLI, never a per-project script. + expect(fs.existsSync(path.join(targetDir, 'scripts', 'deploy.mjs'))).toBe(false); + + if (PAYLOAD_DEPLOY_TEMPLATES.has(template)) { + // Next.js deploys by payload: `.next` is gitignored, so a git reference carries no + // build output, and building on the cluster fails (HarperFast/nextjs#57, #58). + expect(pkgJson.scripts.deploy).toBe('next build && harper deploy_component . restart=true replicated=true'); + expect(pkgJson.scripts['deploy:setup']).toBeUndefined(); + } else { + expect(pkgJson.scripts.deploy).toBe( + 'harper deploy by_ref=true credential=true restart=true replicated=true', + ); + expect(pkgJson.scripts['deploy:setup']).toBe('harper deploy setup=true'); + } }); } }); diff --git a/templates-shared/all/_github/workflows/deploy.yaml b/templates-shared/all/_github/workflows/deploy.yaml new file mode 100644 index 0000000..bf76af9 --- /dev/null +++ b/templates-shared/all/_github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# Deploys this app to your Harper cluster by *reference* via `harper deploy by_ref=true` +# (`your-package-manager-run-here deploy`). +# +# Runs on a version tag (e.g. `git tag v1.2.3 && git push --tags`) or manually. The Harper cluster +# fetches the tagged commit over git+https (pinned by its exact SHA, so peers can't diverge on a +# moved tag), authenticating a private clone with the encrypted token you sealed once via +# `your-package-manager-run-here deploy:setup`. The credential lives on the cluster, so this +# workflow handles no token. +# +# Required repository secrets (Settings → Secrets and variables → Actions): +# HARPER_CLI_TARGET your cluster's operations URL, e.g. https://my-cluster.harperdb.io:9925 +# HARPER_CLI_REFRESH_TOKEN a long-lived refresh token; the CLI mints a short-lived operation +# token from it on each run, so no password is stored in CI +# +# Set both in one command — this pipes them straight from your cluster into GitHub, so the token +# never appears on screen or in your shell history: +# +# harper login --for-ci | gh secret set --env-file - +# +# Public repo? Drop `credential=true` from the `deploy` script — no cluster-side auth needed. +# See the "Deployment" section of the README for details. +name: Deploy to Harper Fabric +on: + workflow_dispatch: + push: + tags: + - 'v*' + +concurrency: + group: deploy + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + fetch-depth: 0 + fetch-tags: true + # your-package-manager-setup-step-here + - name: Set up Node.js + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + with: + # your-package-manager-node-cache-here + node-version-file: '.nvmrc' + - name: Install dependencies + run: your-package-manager-install-here + - name: Run unit tests + run: your-package-manager-run-here test + - name: Run lint + run: your-package-manager-run-here lint + - name: Install Harper CLI + # Installed with npm regardless of this project's package manager: it is a global tool, and + # npm always comes with the Node.js set up above. Pinned to the 5.2 line (where + # deploy-by-reference lands); ^5.2.0 blocks a breaking 6.x bump. + run: npm install -g harper@^5.2.0 + - name: Deploy + run: your-package-manager-run-here deploy + env: + HARPER_CLI_TARGET: ${{ secrets.HARPER_CLI_TARGET }} + HARPER_CLI_REFRESH_TOKEN: ${{ secrets.HARPER_CLI_REFRESH_TOKEN }} diff --git a/templates-shared/applySharedTemplates.js b/templates-shared/applySharedTemplates.js index 1e35766..af588ca 100644 --- a/templates-shared/applySharedTemplates.js +++ b/templates-shared/applySharedTemplates.js @@ -19,11 +19,24 @@ import { copyDir } from '../lib/fs/copyDir.js'; 'template-vue-ts-ssr', ], }; + // template-early-hints is a legacy `harperdb` EdgeWorker example (not published via + // `npm create harper`), so it is intentionally excluded from the deploy-by-reference + // migration — it must not receive the `_github` deploy workflow that targets the modern + // `harper` CLI. It still gets every other shared file. + const excludeGithubForEarlyHints = (src) => !src.split(path.sep).includes('_github'); + + // The Next.js templates are absent from the list above on purpose. They deploy by *payload* + // (`next build && harper deploy_component .`) because `.next` is gitignored — so a git + // reference carries no build output — and building on the cluster currently fails + // (HarperFast/nextjs#57, #58). They keep their own payload deploy workflow; once those issues + // land they can join this fan-out and switch to deploy-by-reference like the rest. + for (const key in copiesToMake) { const fromShared = path.resolve(import.meta.dirname, key); for (const targetTemplate of copiesToMake[key]) { const toTemplate = path.resolve(import.meta.dirname, '..', targetTemplate); - copyDir(fromShared, toTemplate); + const filter = targetTemplate === 'template-early-hints' ? excludeGithubForEarlyHints : undefined; + copyDir(fromShared, toTemplate, filter); } } })(); diff --git a/templates-studio/buildStudioTemplates.js b/templates-studio/buildStudioTemplates.js index dc0467f..dbc8c21 100644 --- a/templates-studio/buildStudioTemplates.js +++ b/templates-studio/buildStudioTemplates.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -import fs from 'node:fs'; import path from 'node:path'; import { studioTemplateNames } from '../lib/constants/templates.js'; import { copyDir } from '../lib/fs/copyDir.js'; @@ -20,13 +19,25 @@ import { run } from '../lib/run.js'; fromTemplate, toTemplate, // Studio apps run deployed on Harper Fabric (no local `npm run dev`), so skip the - // `.claude/launch.json` preview config that only applies to local development. + // `.claude/launch.json` preview config that only applies to local development, and the + // `_github` deploy workflow: Studio deploys from Studio, and `scripts` is emptied below, + // so the workflow's install/test/deploy steps would have nothing to run. // Match on the basename so a clone path containing `_env`/`_claude` doesn't skip everything. (srcFile) => { const filename = path.basename(srcFile); - return !filename.startsWith('_env') && filename !== '_claude'; + return !filename.startsWith('_env') && filename !== '_claude' && filename !== '_github'; }, (sourceContent, targetPath) => { + // Only `npm create harper` knows which package manager invoked it, so it alone can + // substitute the `your-package-manager-*` placeholders. Studio templates are cloned + // rather than scaffolded, so resolve them to npm here instead of shipping the raw + // tokens. Reassigning the parameter, rather than branching on a local, keeps this + // applied on every path out of this function — including the fallback return. + // Longest token first, so a shorter one can never eat a longer one's prefix. + sourceContent = sourceContent + .replaceAll('your-package-manager-run-here', 'npm run') + .replaceAll('your-package-manager-here', 'npm'); + if (targetPath.endsWith('/package.json')) { return sourceContent .replace(/your-package-name-here/g, `@harperfast/${targetTemplate}-studio`) @@ -75,10 +86,6 @@ see what different users will be able to access through your API.`, }, ); - if (fs.existsSync(path.resolve(fromTemplate, '_github'))) { - emptyDir(path.resolve(toTemplate, '.github')); - renameFile(path.resolve(toTemplate, '_github'), path.resolve(toTemplate, '.github')); - } renameFile(path.resolve(toTemplate, '_nvmrc'), path.resolve(toTemplate, '.nvmrc')); renameFile(path.resolve(toTemplate, '_gitignore'), path.resolve(toTemplate, '.gitignore')); renameFile(path.resolve(toTemplate, '_aiignore'), path.resolve(toTemplate, '.aiignore')); diff --git a/vitest.config.js b/vitest.config.js index c6ea4ef..53d2dbc 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -11,6 +11,7 @@ export default defineConfig({ include: [ 'lib/**/*.test.js', 'template.tests/staticConfig.test.js', + 'template.tests/deployWorkflow.test.js', ], }, });