@dotdotdash/afterhours 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -1
- package/dist/index.js +78 -2
- package/package.json +1 -1
- package/templates/config.yml +5 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
- **Dependency audit** — finds vulnerable and outdated packages, upgrades safe deps automatically, uses an LLM agent to fix any breakages
|
|
10
10
|
- **Code optimization** — runs an LLM agent to find and apply code quality improvements, verifies each change with your build + test suite
|
|
11
|
-
- **Deployment** — pushes Docker images, publishes npm packages, creates GitHub Releases, deploys to Vercel or Render
|
|
11
|
+
- **Deployment** — pushes Docker images, publishes npm packages, creates GitHub Releases, deploys to Vercel or Render, or publishes a directory to a git branch (e.g. GitHub Pages)
|
|
12
12
|
- **Platform integrations** — GitHub, GitLab, Azure Repos, AWS CodeCommit, Google Cloud Source (PR/MR creation, auto-merge, commenting)
|
|
13
13
|
- **Notifications** — PR comment, email, or webhook on every run
|
|
14
14
|
- **Scheduler adapters** — GitHub Actions, Docker + cron, or bring your own
|
|
@@ -103,6 +103,24 @@ llm:
|
|
|
103
103
|
|
|
104
104
|
The Copilot account is fully independent from `platform` (repo access) — `apiKeyEnv` here can point to a token for a different GitHub account than `GITHUB_TOKEN`. This lets an org member's Copilot seat drive automated work against a repo (e.g. a client-owned repo) where only a separate PAT has push/PR access. Note that this relies on GitHub's internal Copilot chat-completions API (the same mechanism used by third-party editor integrations); confirm it's compatible with your Copilot license terms before enabling it for unattended/agentic use.
|
|
105
105
|
|
|
106
|
+
### Branch-based deployment (`git-branch` target)
|
|
107
|
+
|
|
108
|
+
Publish a directory to a branch that some other host watches (e.g. `gh-pages` for GitHub Pages):
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
tasks:
|
|
112
|
+
deployment:
|
|
113
|
+
enabled: true
|
|
114
|
+
targets:
|
|
115
|
+
- type: git-branch
|
|
116
|
+
branch: gh-pages
|
|
117
|
+
publishDir: dist # relative to repo root; defaults to the whole repo
|
|
118
|
+
buildCmd: npm run build # optional; omit or set to 'none' to skip
|
|
119
|
+
remote: origin # defaults to 'origin'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This target builds the project (if `buildCmd` is set), checks out the target branch in an isolated `git worktree` (created fresh as an orphan branch if it doesn't exist remotely yet), replaces its contents with `publishDir`, commits, and pushes — without disturbing the main working tree used by the rest of the run. It uses whatever git credentials are already configured for the repo (the same remote/auth the orchestrator uses to push its own branches), so no extra env vars are required.
|
|
123
|
+
|
|
106
124
|
---
|
|
107
125
|
|
|
108
126
|
## Environment variables
|
package/dist/index.js
CHANGED
|
@@ -2679,6 +2679,73 @@ var RenderTarget = class {
|
|
|
2679
2679
|
return `render deploy service ${this.config.serviceIdEnv}`;
|
|
2680
2680
|
}
|
|
2681
2681
|
};
|
|
2682
|
+
var GitBranchTarget = class {
|
|
2683
|
+
constructor(config) {
|
|
2684
|
+
this.config = config;
|
|
2685
|
+
}
|
|
2686
|
+
config;
|
|
2687
|
+
type = "git-branch";
|
|
2688
|
+
validate() {
|
|
2689
|
+
if (!this.config.branch) throw new Error('git-branch target requires a "branch"');
|
|
2690
|
+
}
|
|
2691
|
+
async deploy(ctx) {
|
|
2692
|
+
const remote = this.config.remote ?? "origin";
|
|
2693
|
+
const branch = this.config.branch;
|
|
2694
|
+
const publishDir = this.config.publishDir ?? ".";
|
|
2695
|
+
const worktreeDir = `.afterhours-deploy-${branch.replace(/[^a-zA-Z0-9._-]/g, "-")}`;
|
|
2696
|
+
const message = this.config.commitMessage ?? `Deploy ${branch}${ctx.gitSha ? ` (${ctx.gitSha})` : ""}`;
|
|
2697
|
+
if (this.config.buildCmd && this.config.buildCmd !== "none") {
|
|
2698
|
+
const build = await ctx.sandbox.run(this.config.buildCmd, { cwd: ctx.repoRoot, timeoutSec: 600 });
|
|
2699
|
+
if (build.code !== 0) return { targetType: "git-branch", status: "failed", error: build.stderr || "Build command failed" };
|
|
2700
|
+
}
|
|
2701
|
+
try {
|
|
2702
|
+
await ctx.sandbox.run(`git worktree remove --force "${worktreeDir}"`, { cwd: ctx.repoRoot });
|
|
2703
|
+
await ctx.sandbox.run(`rm -rf "${worktreeDir}"`, { cwd: ctx.repoRoot });
|
|
2704
|
+
await ctx.sandbox.run(`git worktree prune`, { cwd: ctx.repoRoot });
|
|
2705
|
+
await ctx.sandbox.run(`git fetch ${remote} ${branch}`, { cwd: ctx.repoRoot });
|
|
2706
|
+
const remoteCheck = await ctx.sandbox.run(`git ls-remote --exit-code --heads ${remote} ${branch}`, { cwd: ctx.repoRoot });
|
|
2707
|
+
const branchExists = remoteCheck.code === 0;
|
|
2708
|
+
const add = branchExists ? await ctx.sandbox.run(`git worktree add -B ${branch} "${worktreeDir}" ${remote}/${branch}`, { cwd: ctx.repoRoot, timeoutSec: 120 }) : await ctx.sandbox.run(`git worktree add --detach "${worktreeDir}"`, { cwd: ctx.repoRoot, timeoutSec: 120 });
|
|
2709
|
+
if (add.code !== 0) {
|
|
2710
|
+
return { targetType: "git-branch", status: "failed", error: add.stderr || "git worktree add failed" };
|
|
2711
|
+
}
|
|
2712
|
+
if (!branchExists) {
|
|
2713
|
+
const orphan = await ctx.sandbox.run(`git -C "${worktreeDir}" checkout --orphan ${branch}`, { cwd: ctx.repoRoot, timeoutSec: 60 });
|
|
2714
|
+
if (orphan.code !== 0) {
|
|
2715
|
+
return { targetType: "git-branch", status: "failed", error: orphan.stderr || "Failed to create orphan branch" };
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
await ctx.sandbox.run(`git -C "${worktreeDir}" rm -rf . --quiet`, { cwd: ctx.repoRoot, timeoutSec: 60 });
|
|
2719
|
+
const copy = await ctx.sandbox.run(
|
|
2720
|
+
`find "${publishDir}" -mindepth 1 -maxdepth 1 -not -name '.git' -not -name "${worktreeDir}" -exec cp -r {} "${worktreeDir}/" \\;`,
|
|
2721
|
+
{ cwd: ctx.repoRoot, timeoutSec: 120 }
|
|
2722
|
+
);
|
|
2723
|
+
if (copy.code !== 0) {
|
|
2724
|
+
return { targetType: "git-branch", status: "failed", error: copy.stderr || "Failed to copy publish directory contents" };
|
|
2725
|
+
}
|
|
2726
|
+
await ctx.sandbox.run(`git -C "${worktreeDir}" add -A`, { cwd: ctx.repoRoot, timeoutSec: 60 });
|
|
2727
|
+
const diffCheck = await ctx.sandbox.run(`git -C "${worktreeDir}" diff --cached --quiet`, { cwd: ctx.repoRoot, timeoutSec: 30 });
|
|
2728
|
+
if (diffCheck.code === 0) {
|
|
2729
|
+
return { targetType: "git-branch", status: "skipped" };
|
|
2730
|
+
}
|
|
2731
|
+
const commitRes = await ctx.sandbox.run(`git -C "${worktreeDir}" commit -m "${message.replace(/"/g, '\\"')}"`, { cwd: ctx.repoRoot, timeoutSec: 60 });
|
|
2732
|
+
if (commitRes.code !== 0) {
|
|
2733
|
+
return { targetType: "git-branch", status: "failed", error: commitRes.stderr || "git commit failed" };
|
|
2734
|
+
}
|
|
2735
|
+
const pushRes = await ctx.sandbox.run(`git -C "${worktreeDir}" push ${remote} HEAD:${branch}`, { cwd: ctx.repoRoot, timeoutSec: 120 });
|
|
2736
|
+
if (pushRes.code !== 0) {
|
|
2737
|
+
return { targetType: "git-branch", status: "failed", error: pushRes.stderr || "git push failed" };
|
|
2738
|
+
}
|
|
2739
|
+
return { targetType: "git-branch", status: "succeeded", url: `${remote}/${branch}` };
|
|
2740
|
+
} finally {
|
|
2741
|
+
await ctx.sandbox.run(`git worktree remove --force "${worktreeDir}"`, { cwd: ctx.repoRoot }).catch(() => void 0);
|
|
2742
|
+
await ctx.sandbox.run(`rm -rf "${worktreeDir}"`, { cwd: ctx.repoRoot }).catch(() => void 0);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
describe() {
|
|
2746
|
+
return `push ${this.config.publishDir ?? "."} to branch "${this.config.branch}"`;
|
|
2747
|
+
}
|
|
2748
|
+
};
|
|
2682
2749
|
var CloudStubTarget = class {
|
|
2683
2750
|
constructor(config) {
|
|
2684
2751
|
this.config = config;
|
|
@@ -2721,6 +2788,8 @@ function createDeployTarget(config, ctx) {
|
|
|
2721
2788
|
return new VercelTarget(config);
|
|
2722
2789
|
case "render":
|
|
2723
2790
|
return new RenderTarget(config);
|
|
2791
|
+
case "git-branch":
|
|
2792
|
+
return new GitBranchTarget(config);
|
|
2724
2793
|
case "aws":
|
|
2725
2794
|
case "azure":
|
|
2726
2795
|
case "gcp":
|
|
@@ -2759,7 +2828,7 @@ var DeploymentTask = class {
|
|
|
2759
2828
|
anyFailed = true;
|
|
2760
2829
|
continue;
|
|
2761
2830
|
}
|
|
2762
|
-
const result = await target.deploy({ sandbox: ctx.sandbox, repoRoot: ctx.repoRoot });
|
|
2831
|
+
const result = await target.deploy({ sandbox: ctx.sandbox, repoRoot: ctx.repoRoot, gitSha: await getHeadSha(ctx.repoRoot) });
|
|
2763
2832
|
results.push(result);
|
|
2764
2833
|
report.deployments.push(result);
|
|
2765
2834
|
if (result.status === "failed") anyFailed = true;
|
|
@@ -2772,6 +2841,13 @@ var DeploymentTask = class {
|
|
|
2772
2841
|
return { status: succeeded > 0 ? "changed" : "noop", summary: `Deployment: ${succeeded} target(s) deployed.` };
|
|
2773
2842
|
}
|
|
2774
2843
|
};
|
|
2844
|
+
async function getHeadSha(repoRoot) {
|
|
2845
|
+
try {
|
|
2846
|
+
return (await createGit(repoRoot).revparse(["--short", "HEAD"])).trim();
|
|
2847
|
+
} catch {
|
|
2848
|
+
return void 0;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2775
2851
|
|
|
2776
2852
|
// src/cli/run.ts
|
|
2777
2853
|
function registerRun(program) {
|
|
@@ -2905,7 +2981,7 @@ function getTemplateDir() {
|
|
|
2905
2981
|
// src/cli/main.ts
|
|
2906
2982
|
function main() {
|
|
2907
2983
|
const program = new Command();
|
|
2908
|
-
program.name("afterhours").description("afterhours CLI").version("0.1.
|
|
2984
|
+
program.name("afterhours").description("afterhours CLI").version("0.1.1");
|
|
2909
2985
|
registerInit(program);
|
|
2910
2986
|
registerRun(program);
|
|
2911
2987
|
program.command("audit").action(() => console.log("<audit>: not implemented yet"));
|
package/package.json
CHANGED
package/templates/config.yml
CHANGED
|
@@ -66,6 +66,11 @@ tasks:
|
|
|
66
66
|
# - type: github-release
|
|
67
67
|
# tagFrom: package.json
|
|
68
68
|
# generateNotes: true
|
|
69
|
+
# - type: git-branch # push a directory to a branch a host watches (e.g. GitHub Pages)
|
|
70
|
+
# branch: gh-pages
|
|
71
|
+
# publishDir: dist # relative to repo root; defaults to the whole repo
|
|
72
|
+
# buildCmd: npm run build
|
|
73
|
+
# remote: origin
|
|
69
74
|
issueTriage:
|
|
70
75
|
enabled: false # requires a platform token with issue read/write access (e.g. GITHUB_TOKEN)
|
|
71
76
|
allowedAuthors: # usernames allowed to trigger automated issue resolution, or ["*"] for anyone
|