berg-pages 0.1.0-alpha.6
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/CHANGELOG.md +93 -0
- package/LICENSE +183 -0
- package/README.md +93 -0
- package/dist/adapters/local-deploy-execution-ports.d.ts +12 -0
- package/dist/adapters/local-deploy-execution-ports.js +81 -0
- package/dist/adapters/local-environment-probe.d.ts +5 -0
- package/dist/adapters/local-environment-probe.js +54 -0
- package/dist/adapters/local-init-apply-ports.d.ts +10 -0
- package/dist/adapters/local-init-apply-ports.js +47 -0
- package/dist/adapters/local-project-inspector.d.ts +4 -0
- package/dist/adapters/local-project-inspector.js +184 -0
- package/dist/application/contracts/plugins/deploy-mode-plugin.d.ts +20 -0
- package/dist/application/contracts/plugins/deploy-mode-plugin.js +1 -0
- package/dist/application/contracts/plugins/framework-plugin.d.ts +19 -0
- package/dist/application/contracts/plugins/framework-plugin.js +1 -0
- package/dist/application/contracts/ports/environment-probe-port.d.ts +4 -0
- package/dist/application/contracts/ports/environment-probe-port.js +1 -0
- package/dist/application/contracts/ports/project-inspector-port.d.ts +28 -0
- package/dist/application/contracts/ports/project-inspector-port.js +1 -0
- package/dist/application/contracts/project-context.d.ts +6 -0
- package/dist/application/contracts/project-context.js +1 -0
- package/dist/application/index.d.ts +11 -0
- package/dist/application/index.js +11 -0
- package/dist/application/usecases/apply-deploy-plan.d.ts +8 -0
- package/dist/application/usecases/apply-deploy-plan.js +100 -0
- package/dist/application/usecases/apply-init-plan.d.ts +13 -0
- package/dist/application/usecases/apply-init-plan.js +65 -0
- package/dist/application/usecases/build-deploy-plan.d.ts +9 -0
- package/dist/application/usecases/build-deploy-plan.js +177 -0
- package/dist/application/usecases/build-doctor-plan.d.ts +9 -0
- package/dist/application/usecases/build-doctor-plan.js +235 -0
- package/dist/application/usecases/build-init-plan.d.ts +9 -0
- package/dist/application/usecases/build-init-plan.js +140 -0
- package/dist/application/usecases/create-project-context.d.ts +8 -0
- package/dist/application/usecases/create-project-context.js +168 -0
- package/dist/application/usecases/workflow-diagnostics.d.ts +13 -0
- package/dist/application/usecases/workflow-diagnostics.js +137 -0
- package/dist/core/diagnostics/diagnostic.d.ts +11 -0
- package/dist/core/diagnostics/diagnostic.js +1 -0
- package/dist/core/entities/command.d.ts +1 -0
- package/dist/core/entities/command.js +1 -0
- package/dist/core/entities/deploy-mode.d.ts +6 -0
- package/dist/core/entities/deploy-mode.js +1 -0
- package/dist/core/entities/environment.d.ts +16 -0
- package/dist/core/entities/environment.js +1 -0
- package/dist/core/entities/framework.d.ts +8 -0
- package/dist/core/entities/framework.js +1 -0
- package/dist/core/entities/pages-target.d.ts +22 -0
- package/dist/core/entities/pages-target.js +1 -0
- package/dist/core/entities/project.d.ts +45 -0
- package/dist/core/entities/project.js +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +13 -0
- package/dist/core/plans/assumption.d.ts +7 -0
- package/dist/core/plans/assumption.js +1 -0
- package/dist/core/plans/build-plan.d.ts +12 -0
- package/dist/core/plans/build-plan.js +1 -0
- package/dist/core/plans/command-plan.d.ts +27 -0
- package/dist/core/plans/command-plan.js +45 -0
- package/dist/core/plans/deploy-plan.d.ts +17 -0
- package/dist/core/plans/deploy-plan.js +1 -0
- package/dist/core/plans/plan-step.d.ts +59 -0
- package/dist/core/plans/plan-step.js +1 -0
- package/dist/core/plans/template-plan.d.ts +11 -0
- package/dist/core/plans/template-plan.js +1 -0
- package/dist/features/deployment-modes/git-pages-deploy-mode-plugin.d.ts +12 -0
- package/dist/features/deployment-modes/git-pages-deploy-mode-plugin.js +49 -0
- package/dist/features/frameworks/astro-framework-plugin.d.ts +36 -0
- package/dist/features/frameworks/astro-framework-plugin.js +97 -0
- package/dist/features/frameworks/static-html-framework-plugin.d.ts +36 -0
- package/dist/features/frameworks/static-html-framework-plugin.js +94 -0
- package/dist/features/frameworks/vite-framework-plugin.d.ts +36 -0
- package/dist/features/frameworks/vite-framework-plugin.js +97 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/interfaces/cli/main.d.ts +2 -0
- package/dist/interfaces/cli/main.js +157 -0
- package/dist/interfaces/cli/render-command-plan.d.ts +2 -0
- package/dist/interfaces/cli/render-command-plan.js +87 -0
- package/docs/CANARY.md +117 -0
- package/docs/COMPATIBILITY.md +88 -0
- package/docs/DEPLOY-SAFETY.md +113 -0
- package/package.json +83 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { LocalDeployExecutionPorts } from "../../adapters/local-deploy-execution-ports.js";
|
|
4
|
+
import { LocalEnvironmentProbe } from "../../adapters/local-environment-probe.js";
|
|
5
|
+
import { LocalInitApplyPorts } from "../../adapters/local-init-apply-ports.js";
|
|
6
|
+
import { LocalProjectInspector } from "../../adapters/local-project-inspector.js";
|
|
7
|
+
import { applyDeployPlan } from "../../application/usecases/apply-deploy-plan.js";
|
|
8
|
+
import { applyInitPlan } from "../../application/usecases/apply-init-plan.js";
|
|
9
|
+
import { createProjectContext } from "../../application/usecases/create-project-context.js";
|
|
10
|
+
import { buildDoctorPlan } from "../../application/usecases/build-doctor-plan.js";
|
|
11
|
+
import { buildDeployPlan } from "../../application/usecases/build-deploy-plan.js";
|
|
12
|
+
import { buildInitPlan } from "../../application/usecases/build-init-plan.js";
|
|
13
|
+
import { GitPagesDeployModePlugin } from "../../features/deployment-modes/git-pages-deploy-mode-plugin.js";
|
|
14
|
+
import { AstroFrameworkPlugin } from "../../features/frameworks/astro-framework-plugin.js";
|
|
15
|
+
import { StaticHtmlFrameworkPlugin } from "../../features/frameworks/static-html-framework-plugin.js";
|
|
16
|
+
import { ViteFrameworkPlugin } from "../../features/frameworks/vite-framework-plugin.js";
|
|
17
|
+
import { renderCommandPlan } from "./render-command-plan.js";
|
|
18
|
+
async function main(argv) {
|
|
19
|
+
const command = argv[0];
|
|
20
|
+
const args = argv.slice(1);
|
|
21
|
+
if (!command || command === "--help" || command === "help") {
|
|
22
|
+
printUsage();
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
const projectRootArgument = args.find((arg) => !arg.startsWith("--"));
|
|
26
|
+
const projectRoot = resolve(projectRootArgument ?? process.cwd());
|
|
27
|
+
const inspector = new LocalProjectInspector();
|
|
28
|
+
const environmentProbe = new LocalEnvironmentProbe();
|
|
29
|
+
const frameworkPlugins = [
|
|
30
|
+
new ViteFrameworkPlugin(),
|
|
31
|
+
new StaticHtmlFrameworkPlugin(),
|
|
32
|
+
new AstroFrameworkPlugin()
|
|
33
|
+
];
|
|
34
|
+
const gitPagesPlugin = new GitPagesDeployModePlugin();
|
|
35
|
+
const deployModePlugins = [gitPagesPlugin];
|
|
36
|
+
const [inspection, environment] = await Promise.all([
|
|
37
|
+
inspector.inspect(projectRoot),
|
|
38
|
+
environmentProbe.probe(projectRoot)
|
|
39
|
+
]);
|
|
40
|
+
const context = createProjectContext({
|
|
41
|
+
inspection,
|
|
42
|
+
environment
|
|
43
|
+
});
|
|
44
|
+
if (command === "doctor") {
|
|
45
|
+
const plan = buildDoctorPlan({
|
|
46
|
+
context,
|
|
47
|
+
frameworkPlugins,
|
|
48
|
+
deployModePlugins
|
|
49
|
+
});
|
|
50
|
+
console.log(renderCommandPlan(plan));
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
if (command === "init") {
|
|
54
|
+
const shouldDryRun = args.includes("--dry-run");
|
|
55
|
+
const shouldApply = args.includes("--apply");
|
|
56
|
+
if (!shouldDryRun && !shouldApply) {
|
|
57
|
+
console.error("Use `berg-pages init --dry-run` or `berg-pages init --apply`.");
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
if (shouldDryRun && shouldApply) {
|
|
61
|
+
console.error("Choose either `--dry-run` or `--apply`, not both.");
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
const frameworkPlugin = frameworkPlugins.find((plugin) => plugin.id === context.project.framework?.id);
|
|
65
|
+
const deployModePlugin = context.project.pages.deployMode?.id === gitPagesPlugin.id && context.project.pages.target
|
|
66
|
+
? gitPagesPlugin
|
|
67
|
+
: undefined;
|
|
68
|
+
const plan = buildInitPlan({
|
|
69
|
+
context,
|
|
70
|
+
...(frameworkPlugin ? { frameworkPlugin } : {}),
|
|
71
|
+
...(deployModePlugin ? { deployModePlugin } : {})
|
|
72
|
+
});
|
|
73
|
+
console.log(renderCommandPlan(plan));
|
|
74
|
+
if (shouldApply) {
|
|
75
|
+
const result = await applyInitPlan(plan, new LocalInitApplyPorts(projectRoot));
|
|
76
|
+
if (result.diagnostics.length > 0) {
|
|
77
|
+
console.error("");
|
|
78
|
+
console.error("Apply failed:");
|
|
79
|
+
for (const diagnostic of result.diagnostics) {
|
|
80
|
+
console.error(`- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.summary}`);
|
|
81
|
+
}
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
console.log("");
|
|
85
|
+
console.log(`Applied ${result.appliedStepIds.length} local init steps.`);
|
|
86
|
+
}
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
if (command === "deploy") {
|
|
90
|
+
const shouldDryRun = args.includes("--dry-run");
|
|
91
|
+
const shouldApply = args.includes("--apply");
|
|
92
|
+
const confirmedPush = args.includes("--confirm-push");
|
|
93
|
+
if (!shouldDryRun && !shouldApply) {
|
|
94
|
+
console.error("Use `berg-pages deploy --dry-run` or `berg-pages deploy --apply --confirm-push`.");
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
if (shouldDryRun && shouldApply) {
|
|
98
|
+
console.error("Choose either `--dry-run` or `--apply`, not both.");
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
if (shouldDryRun && confirmedPush) {
|
|
102
|
+
console.error("Use `--confirm-push` only with `deploy --apply`.");
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
if (shouldApply && !confirmedPush) {
|
|
106
|
+
console.error("Refusing deploy apply without `--confirm-push`.");
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
const frameworkPlugin = frameworkPlugins.find((plugin) => plugin.id === context.project.framework?.id);
|
|
110
|
+
const deployModePlugin = context.project.pages.deployMode?.id === gitPagesPlugin.id && context.project.pages.target
|
|
111
|
+
? gitPagesPlugin
|
|
112
|
+
: undefined;
|
|
113
|
+
const plan = buildDeployPlan({
|
|
114
|
+
context,
|
|
115
|
+
...(frameworkPlugin ? { frameworkPlugin } : {}),
|
|
116
|
+
...(deployModePlugin ? { deployModePlugin } : {})
|
|
117
|
+
});
|
|
118
|
+
console.log(renderCommandPlan(plan));
|
|
119
|
+
if (shouldApply) {
|
|
120
|
+
const result = await applyDeployPlan(plan, new LocalDeployExecutionPorts(projectRoot));
|
|
121
|
+
if (result.diagnostics.length > 0) {
|
|
122
|
+
console.error("");
|
|
123
|
+
console.error("Deploy apply failed:");
|
|
124
|
+
for (const diagnostic of result.diagnostics) {
|
|
125
|
+
console.error(`- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.summary}`);
|
|
126
|
+
if (diagnostic.detail) {
|
|
127
|
+
console.error(` ${diagnostic.detail}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(`Applied ${result.appliedStepIds.length} deploy publish steps.`);
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
console.error(`Unknown command: ${command}`);
|
|
138
|
+
printUsage();
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
function printUsage() {
|
|
142
|
+
console.log("Usage:");
|
|
143
|
+
console.log(" npm run berg -- doctor [project-root]");
|
|
144
|
+
console.log(" npm run berg -- init --dry-run [project-root]");
|
|
145
|
+
console.log(" npm run berg -- init --apply [project-root]");
|
|
146
|
+
console.log(" npm run berg -- deploy --dry-run [project-root]");
|
|
147
|
+
console.log(" npm run berg -- deploy --apply --confirm-push [project-root]");
|
|
148
|
+
}
|
|
149
|
+
main(process.argv.slice(2))
|
|
150
|
+
.then((exitCode) => {
|
|
151
|
+
process.exitCode = exitCode;
|
|
152
|
+
})
|
|
153
|
+
.catch((error) => {
|
|
154
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
155
|
+
console.error(message);
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export function renderCommandPlan(plan) {
|
|
2
|
+
const lines = [];
|
|
3
|
+
const owner = plan.project.repository.owner;
|
|
4
|
+
const repositoryLabel = owner
|
|
5
|
+
? `${owner}/${plan.project.repository.name}`
|
|
6
|
+
: plan.project.repository.name;
|
|
7
|
+
lines.push(`${plan.command} plan`);
|
|
8
|
+
lines.push(`Project: ${repositoryLabel}`);
|
|
9
|
+
lines.push(plan.summary);
|
|
10
|
+
lines.push("");
|
|
11
|
+
if (plan.project.framework) {
|
|
12
|
+
lines.push(`Framework: ${plan.project.framework.id} (${plan.project.framework.source}, ${plan.project.framework.confidence})`);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
lines.push("Framework: unknown");
|
|
16
|
+
}
|
|
17
|
+
lines.push(`Remote: ${plan.project.repository.remoteUrl ?? "missing"}`);
|
|
18
|
+
if (plan.project.pages.target?.kind === "codeberg-project") {
|
|
19
|
+
lines.push(`Pages target: ${plan.project.pages.target.url}`);
|
|
20
|
+
lines.push(`Base path: ${plan.project.pages.target.basePath}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
lines.push("Pages target: missing");
|
|
24
|
+
lines.push("Base path: missing");
|
|
25
|
+
}
|
|
26
|
+
lines.push(`Deploy mode: ${plan.project.pages.deployMode?.id ?? "missing"}`);
|
|
27
|
+
lines.push(`Build output: ${plan.project.paths.buildOutputDir ?? "missing"}`);
|
|
28
|
+
lines.push("");
|
|
29
|
+
lines.push("Diagnostics:");
|
|
30
|
+
if (plan.diagnostics.length === 0) {
|
|
31
|
+
lines.push("- none");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
for (const diagnostic of plan.diagnostics) {
|
|
35
|
+
lines.push(`- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.summary}`);
|
|
36
|
+
if (diagnostic.detail) {
|
|
37
|
+
lines.push(` ${diagnostic.detail}`);
|
|
38
|
+
}
|
|
39
|
+
if (diagnostic.suggestions && diagnostic.suggestions.length > 0) {
|
|
40
|
+
lines.push(" Suggestions:");
|
|
41
|
+
lines.push(...diagnostic.suggestions.map((suggestion) => ` - ${suggestion}`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push("Steps:");
|
|
47
|
+
for (const step of plan.steps) {
|
|
48
|
+
lines.push(...renderStep(step));
|
|
49
|
+
}
|
|
50
|
+
lines.push("");
|
|
51
|
+
lines.push(`Effects: ${plan.effectSummary.writes} writes, ${plan.effectSummary.directories} directories, ${plan.effectSummary.commands} commands, ${plan.effectSummary.publishes} publishes, ${plan.effectSummary.verifications} verifications, ${plan.effectSummary.notes} notes`);
|
|
52
|
+
lines.push(`Safe to apply: ${plan.safeToApply ? "yes" : "no"}`);
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
function renderStep(step) {
|
|
56
|
+
switch (step.kind) {
|
|
57
|
+
case "verify":
|
|
58
|
+
return [`- verify ${step.subject} [${step.status}]`, ...(step.detail ? [` ${step.detail}`] : [])];
|
|
59
|
+
case "note":
|
|
60
|
+
return [`- note ${step.category}: ${step.message}`, ...(step.detail ? [` ${step.detail}`] : [])];
|
|
61
|
+
case "ensure-directory":
|
|
62
|
+
return [`- ensure directory ${step.path}`, ` ${step.reason}`];
|
|
63
|
+
case "write-file":
|
|
64
|
+
return [
|
|
65
|
+
`- write file ${step.path} (${step.mode})`,
|
|
66
|
+
` ${step.reason}`,
|
|
67
|
+
" preview:",
|
|
68
|
+
...step.content.split("\n").map((line) => ` ${line}`)
|
|
69
|
+
];
|
|
70
|
+
case "delete-file":
|
|
71
|
+
return [`- delete file ${step.path}`, ` ${step.reason}`];
|
|
72
|
+
case "run-command":
|
|
73
|
+
return [
|
|
74
|
+
`- run ${step.executable} ${step.args.join(" ")}`.trim(),
|
|
75
|
+
` ${step.reason}`
|
|
76
|
+
];
|
|
77
|
+
case "publish-site":
|
|
78
|
+
return [
|
|
79
|
+
`- publish ${step.outputDir} to ${step.destination}`,
|
|
80
|
+
` source: ${step.sourcePath}`,
|
|
81
|
+
` mode: ${step.modeId}, target: ${step.targetKind}`,
|
|
82
|
+
` remote: ${step.remote}, branch: ${step.branch}, force: ${step.force ? "yes" : "no"}`,
|
|
83
|
+
" operations:",
|
|
84
|
+
...step.operations.map((operation) => ` - ${operation}`)
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
}
|
package/docs/CANARY.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Canary Validation
|
|
2
|
+
|
|
3
|
+
This document records real-world disposable deploy validation.
|
|
4
|
+
|
|
5
|
+
Status: passed.
|
|
6
|
+
|
|
7
|
+
`deploy --apply --confirm-push` has been validated against a disposable Codeberg repository for the supported Vite to Codeberg Pages `git-pages` path.
|
|
8
|
+
|
|
9
|
+
## Attempt Log
|
|
10
|
+
|
|
11
|
+
### 2026-05-27
|
|
12
|
+
|
|
13
|
+
Result: blocked before remote mutation.
|
|
14
|
+
|
|
15
|
+
What was checked:
|
|
16
|
+
|
|
17
|
+
- Local worktree was clean and synced with `origin/master`.
|
|
18
|
+
- `https://codeberg.org/nyigoro/berg-pages-canary.git` does not exist yet.
|
|
19
|
+
- No local `tea` CLI was available.
|
|
20
|
+
- No `CODEBERG`, `FORGEJO`, `GITEA`, or `TEA` token environment variable was available.
|
|
21
|
+
- Chrome was running, but the Codex Chrome Extension was not installed in the selected Chrome profile, so the authenticated Codeberg browser session could not be automated.
|
|
22
|
+
|
|
23
|
+
Next unblock:
|
|
24
|
+
|
|
25
|
+
- Create a disposable public Codeberg repository named `berg-pages-canary`, or provide an authenticated Codeberg creation path for that repository.
|
|
26
|
+
- Then rerun the planned canary checklist below.
|
|
27
|
+
|
|
28
|
+
### 2026-05-27 Passing Canary
|
|
29
|
+
|
|
30
|
+
Date: 2026-05-27
|
|
31
|
+
Codeberg repository: https://codeberg.org/nyigoro/berg-pages-canary
|
|
32
|
+
Local canary path: `C:\Users\Donald\AppData\Local\Temp\berg-pages-canary-run`
|
|
33
|
+
Local source branch before: `main`
|
|
34
|
+
Local source branch after: `main`
|
|
35
|
+
Local source commit before: `ecb2fd98d89d40ceeec245c3c31e5d0e2624e312`
|
|
36
|
+
Local source commit after: `ecb2fd98d89d40ceeec245c3c31e5d0e2624e312`
|
|
37
|
+
Dry-run output saved at: [canary/2026-05-27-deploy-dry-run.txt](canary/2026-05-27-deploy-dry-run.txt)
|
|
38
|
+
Apply result: `Applied 1 deploy publish steps.`
|
|
39
|
+
Remote pages branch commit: `75789b8ff7093bdea59c07b45f51b722f0ae27d2`
|
|
40
|
+
Remote pages branch tree: `index.html`
|
|
41
|
+
Pages URL: https://nyigoro.codeberg.page/berg-pages-canary/
|
|
42
|
+
Pages URL verification: HTTP 200 and response contained `berg-pages canary deployed`
|
|
43
|
+
Unexpected source worktree changes: none from deploy apply; expected local `init --apply` and `dist` files remained uncommitted in the disposable canary worktree
|
|
44
|
+
Temporary publish cleanup notes: no `berg-pages-publish-*` directories remained in the temp directory after deploy apply
|
|
45
|
+
Result: pass
|
|
46
|
+
Notes: The first verification clone hit a transient Codeberg TLS close, then succeeded on retry. No product behavior was changed during the canary.
|
|
47
|
+
|
|
48
|
+
## Planned Canary
|
|
49
|
+
|
|
50
|
+
Use a throwaway public Codeberg repository and the supported path only:
|
|
51
|
+
|
|
52
|
+
- Vite or minimal static project
|
|
53
|
+
- generated `dist/index.html`
|
|
54
|
+
- Codeberg project Pages target
|
|
55
|
+
- `git-pages` deployment mode
|
|
56
|
+
- no custom domain
|
|
57
|
+
- no legacy v2 behavior
|
|
58
|
+
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
Replace `<repo-path>` with the local checkout of the disposable repository.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run berg -- doctor <repo-path>
|
|
65
|
+
npm run berg -- init --dry-run <repo-path>
|
|
66
|
+
npm run berg -- init --apply <repo-path>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Create or build the minimal output:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
mkdir -p <repo-path>/dist
|
|
73
|
+
printf '<!doctype html><title>berg-pages canary</title>\n' > <repo-path>/dist/index.html
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then validate deploy:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm run berg -- deploy --dry-run <repo-path>
|
|
80
|
+
npm run berg -- deploy --apply --confirm-push <repo-path>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Pass Checklist
|
|
84
|
+
|
|
85
|
+
- `doctor` identifies the supported Codeberg project Pages path.
|
|
86
|
+
- `init --dry-run` shows only the intended workflow creation.
|
|
87
|
+
- `init --apply` writes only the planned workflow file.
|
|
88
|
+
- `deploy --dry-run` shows publish source `dist`.
|
|
89
|
+
- `deploy --dry-run` shows the expected Codeberg Pages URL.
|
|
90
|
+
- `deploy --dry-run` shows the expected Codeberg remote.
|
|
91
|
+
- `deploy --dry-run` shows branch `pages`.
|
|
92
|
+
- `deploy --dry-run` shows force-push behavior.
|
|
93
|
+
- `deploy --apply --confirm-push` succeeds.
|
|
94
|
+
- Source branch stays unchanged.
|
|
95
|
+
- Remote `pages` branch contains only intended output.
|
|
96
|
+
- Published Pages URL responds.
|
|
97
|
+
- Temporary publish repository cleanup is confirmed as far as practical.
|
|
98
|
+
|
|
99
|
+
## Result Template
|
|
100
|
+
|
|
101
|
+
Use this template after the canary runs.
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
Date:
|
|
105
|
+
Codeberg repository:
|
|
106
|
+
Local source branch before:
|
|
107
|
+
Local source branch after:
|
|
108
|
+
Dry-run output saved at:
|
|
109
|
+
Apply result:
|
|
110
|
+
Remote pages branch commit:
|
|
111
|
+
Pages URL:
|
|
112
|
+
Pages URL verification:
|
|
113
|
+
Unexpected source worktree changes:
|
|
114
|
+
Temporary publish cleanup notes:
|
|
115
|
+
Result: pass/fail
|
|
116
|
+
Notes:
|
|
117
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Compatibility
|
|
2
|
+
|
|
3
|
+
This document describes the project shapes `berg-pages` currently understands and how contributors should think about compatibility changes.
|
|
4
|
+
|
|
5
|
+
`berg-pages` is intentionally conservative. A project that is not clearly supported should receive diagnostics or safe refusal, not guessed deployment behavior.
|
|
6
|
+
|
|
7
|
+
## Supported Project Shapes
|
|
8
|
+
|
|
9
|
+
The current alpha supports Codeberg project Pages with the `git-pages` deployment mode for these project shapes:
|
|
10
|
+
|
|
11
|
+
| Shape | Detection signals | Expected output |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Vite | `vite.config.*`, a `vite` dependency, or a Vite package script | `dist` |
|
|
14
|
+
| Astro | `astro.config.*`, an `astro` dependency, or an Astro package script | `dist` |
|
|
15
|
+
| Plain static | existing `dist` or `public` directory | `dist` preferred, then `public` |
|
|
16
|
+
|
|
17
|
+
Supported commands for these shapes:
|
|
18
|
+
|
|
19
|
+
- `berg-pages doctor .`
|
|
20
|
+
- `berg-pages init --dry-run .`
|
|
21
|
+
- `berg-pages init --apply .`
|
|
22
|
+
- `berg-pages deploy --dry-run .`
|
|
23
|
+
- `berg-pages deploy --apply --confirm-push .`
|
|
24
|
+
|
|
25
|
+
Deploy commands do not run builds. The expected output directory must already exist before deploy planning can become safe.
|
|
26
|
+
|
|
27
|
+
## Current Limits
|
|
28
|
+
|
|
29
|
+
These shapes are intentionally outside the current supported path:
|
|
30
|
+
|
|
31
|
+
- custom-domain deploy
|
|
32
|
+
- legacy Codeberg Pages v2 behavior
|
|
33
|
+
- root-only static sites that publish directly from repository root
|
|
34
|
+
- framework-specific custom output directories
|
|
35
|
+
- hosted CI execution beyond workflow file planning
|
|
36
|
+
- frameworks other than Vite, Astro, and plain static output
|
|
37
|
+
|
|
38
|
+
Unsupported shapes should produce diagnostics such as missing framework, missing output directory, missing target, or workflow conflict warnings.
|
|
39
|
+
|
|
40
|
+
## Custom Domains
|
|
41
|
+
|
|
42
|
+
`berg-pages` can detect a root `.domains` file and report it in `doctor`.
|
|
43
|
+
|
|
44
|
+
That support is diagnostic only:
|
|
45
|
+
|
|
46
|
+
- `doctor` may warn that custom-domain behavior is outside the supported `git-pages` path.
|
|
47
|
+
- `init --dry-run` must not plan `.domains` changes.
|
|
48
|
+
- `deploy --dry-run` must not add custom-domain publish behavior.
|
|
49
|
+
- No command should write or modify `.domains`.
|
|
50
|
+
|
|
51
|
+
## Existing Workflows
|
|
52
|
+
|
|
53
|
+
Repositories with existing `.forgejo/workflows/*.yml` or `.forgejo/workflows/*.yaml` files are treated conservatively.
|
|
54
|
+
|
|
55
|
+
If a workflow already exists outside the generated Pages workflow, `berg-pages` should:
|
|
56
|
+
|
|
57
|
+
- warn in `doctor`
|
|
58
|
+
- block generated workflow planning in `init --dry-run`
|
|
59
|
+
- block deploy planning when the existing workflow may already publish or mutate deployment state
|
|
60
|
+
|
|
61
|
+
This avoids silently adding a generated Pages workflow beside another deployment workflow.
|
|
62
|
+
|
|
63
|
+
## Validation Expectations
|
|
64
|
+
|
|
65
|
+
Compatibility changes should be validated with:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run typecheck
|
|
69
|
+
npm run build
|
|
70
|
+
npm test
|
|
71
|
+
npm pack --dry-run
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
When validating a project shape manually, prefer non-mutating commands:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
berg-pages doctor .
|
|
78
|
+
berg-pages init --dry-run .
|
|
79
|
+
berg-pages deploy --dry-run .
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Do not use `init --apply`, `deploy --apply`, or push-capable behavior for compatibility exploration unless that mutation path is explicitly in scope.
|
|
83
|
+
|
|
84
|
+
## Known Follow-Ups
|
|
85
|
+
|
|
86
|
+
Root-level static Pages repositories are not yet supported unless they expose `dist` or `public`.
|
|
87
|
+
|
|
88
|
+
If real-world validation keeps finding root `index.html` repositories, that should become its own milestone with acceptance criteria before implementation.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Deploy Safety
|
|
2
|
+
|
|
3
|
+
This document describes what `berg-pages deploy` can and cannot mutate.
|
|
4
|
+
|
|
5
|
+
## Supported Path
|
|
6
|
+
|
|
7
|
+
The first deploy path is intentionally narrow:
|
|
8
|
+
|
|
9
|
+
- Vite project
|
|
10
|
+
- Codeberg project Pages URL under `codeberg.page`
|
|
11
|
+
- `git-pages` deployment mode
|
|
12
|
+
- generated site already present in `dist`
|
|
13
|
+
- publish branch named `pages`
|
|
14
|
+
- explicit apply command: `berg-pages deploy --apply --confirm-push`
|
|
15
|
+
|
|
16
|
+
Codeberg Pages currently publishes `codeberg.page` sites through the newer `git-pages` service by pushing content to a branch named `pages`.
|
|
17
|
+
|
|
18
|
+
## Dry Run
|
|
19
|
+
|
|
20
|
+
`berg-pages deploy --dry-run` is non-mutating.
|
|
21
|
+
|
|
22
|
+
It may inspect local files and git metadata, but it must not:
|
|
23
|
+
|
|
24
|
+
- write files
|
|
25
|
+
- run build commands
|
|
26
|
+
- create commits
|
|
27
|
+
- create, switch, reset, or delete branches
|
|
28
|
+
- push to a remote
|
|
29
|
+
- contact Codeberg
|
|
30
|
+
- publish site content
|
|
31
|
+
|
|
32
|
+
The dry-run plan must show:
|
|
33
|
+
|
|
34
|
+
- publish source directory
|
|
35
|
+
- Pages target URL
|
|
36
|
+
- remote URL
|
|
37
|
+
- publish branch
|
|
38
|
+
- whether the future push is forced
|
|
39
|
+
- the high-level git operations a future apply would perform
|
|
40
|
+
|
|
41
|
+
## Apply
|
|
42
|
+
|
|
43
|
+
`berg-pages deploy --apply --confirm-push` is push-capable.
|
|
44
|
+
|
|
45
|
+
It may:
|
|
46
|
+
|
|
47
|
+
- copy the planned output directory into a temporary publish repository
|
|
48
|
+
- create a temporary commit from those copied files
|
|
49
|
+
- force push that temporary commit to the planned `pages` branch
|
|
50
|
+
- remove the temporary publish repository after success or failure
|
|
51
|
+
|
|
52
|
+
It must not:
|
|
53
|
+
|
|
54
|
+
- run `npm run build`
|
|
55
|
+
- write generated files into the source project
|
|
56
|
+
- create commits on the source branch
|
|
57
|
+
- switch the source worktree branch
|
|
58
|
+
- push to a remote other than the planned Codeberg remote
|
|
59
|
+
- push to a branch other than `pages`
|
|
60
|
+
- execute hidden file, branch, or command steps not shown in the deploy plan
|
|
61
|
+
|
|
62
|
+
## Force Push
|
|
63
|
+
|
|
64
|
+
The current supported deploy apply path force-pushes the `pages` branch.
|
|
65
|
+
|
|
66
|
+
That is intentional because the publish branch is treated as generated output. It also means existing content on the remote `pages` branch can be replaced. The dry-run plan must make this visible before apply.
|
|
67
|
+
|
|
68
|
+
## Temporary Repository
|
|
69
|
+
|
|
70
|
+
Deploy apply creates a temporary repository outside the source worktree.
|
|
71
|
+
|
|
72
|
+
The source worktree should remain untouched by publish mechanics:
|
|
73
|
+
|
|
74
|
+
- no source branch switch
|
|
75
|
+
- no source branch commit
|
|
76
|
+
- no source worktree reset
|
|
77
|
+
- no source `.git` mutation for the publish operation
|
|
78
|
+
|
|
79
|
+
The temporary repository is removed in a cleanup step even when publish fails.
|
|
80
|
+
|
|
81
|
+
## `.domains`
|
|
82
|
+
|
|
83
|
+
Custom domains are not supported by the current deploy path.
|
|
84
|
+
|
|
85
|
+
Deploy apply does not fetch an existing `pages` branch to preserve a remote `.domains` file. If a `.domains` file is present inside the planned output directory, it is copied with the rest of the output. Otherwise, any existing `.domains` file on the remote `pages` branch may be replaced by the force push.
|
|
86
|
+
|
|
87
|
+
Do not use this deploy path for custom-domain sites until custom-domain acceptance criteria exist.
|
|
88
|
+
|
|
89
|
+
## Refusal Behavior
|
|
90
|
+
|
|
91
|
+
Deploy apply must refuse before publishing when:
|
|
92
|
+
|
|
93
|
+
- `--confirm-push` is missing
|
|
94
|
+
- the deploy plan has blocking diagnostics
|
|
95
|
+
- the publish step is missing or ambiguous
|
|
96
|
+
- the publish step targets a non-Codeberg remote
|
|
97
|
+
- the publish step targets a branch other than `pages`
|
|
98
|
+
- the plan includes hidden run-command, file, branch, or directory mutation steps
|
|
99
|
+
|
|
100
|
+
## Canary Checklist
|
|
101
|
+
|
|
102
|
+
Before an alpha tag, run one disposable real-world canary:
|
|
103
|
+
|
|
104
|
+
- create a throwaway Codeberg repository
|
|
105
|
+
- add a tiny `dist/index.html`
|
|
106
|
+
- run `deploy --dry-run`
|
|
107
|
+
- confirm the rendered source, target, remote, branch, and force-push operation
|
|
108
|
+
- run `deploy --apply --confirm-push`
|
|
109
|
+
- verify only the remote `pages` branch changed
|
|
110
|
+
- verify the source branch stayed untouched
|
|
111
|
+
- verify the published Pages URL works
|
|
112
|
+
|
|
113
|
+
Record the result in [CANARY.md](CANARY.md). Do not expand deploy scope or tag an alpha until this canary passes.
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "berg-pages",
|
|
3
|
+
"version": "0.1.0-alpha.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A plan-first Codeberg Pages onboarding and deployment CLI.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://codeberg.org/nyigoro/berg-pages.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://codeberg.org/nyigoro/berg-pages",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://codeberg.org/nyigoro/berg-pages/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"bin": {
|
|
18
|
+
"berg-pages": "dist/interfaces/cli/main.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"docs/COMPATIBILITY.md",
|
|
26
|
+
"docs/DEPLOY-SAFETY.md",
|
|
27
|
+
"docs/CANARY.md"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"berg": "tsx src/interfaces/cli/main.ts",
|
|
34
|
+
"build": "tsc -p tsconfig.build.json",
|
|
35
|
+
"prepack": "npm run build",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^24.10.1",
|
|
41
|
+
"tsx": "^4.22.3",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vitest": "^4.0.8"
|
|
44
|
+
},
|
|
45
|
+
"directories": {
|
|
46
|
+
"doc": "docs",
|
|
47
|
+
"test": "tests"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"assertion-error": "^2.0.1",
|
|
51
|
+
"chai": "^6.2.2",
|
|
52
|
+
"convert-source-map": "^2.0.0",
|
|
53
|
+
"detect-libc": "^2.1.2",
|
|
54
|
+
"es-module-lexer": "^2.1.0",
|
|
55
|
+
"esbuild": "^0.28.0",
|
|
56
|
+
"estree-walker": "^3.0.3",
|
|
57
|
+
"expect-type": "^1.3.0",
|
|
58
|
+
"fdir": "^6.5.0",
|
|
59
|
+
"lightningcss": "^1.32.0",
|
|
60
|
+
"lightningcss-win32-x64-msvc": "^1.32.0",
|
|
61
|
+
"magic-string": "^0.30.21",
|
|
62
|
+
"nanoid": "^3.3.12",
|
|
63
|
+
"obug": "^2.1.1",
|
|
64
|
+
"pathe": "^2.0.3",
|
|
65
|
+
"picocolors": "^1.1.1",
|
|
66
|
+
"picomatch": "^4.0.4",
|
|
67
|
+
"postcss": "^8.5.15",
|
|
68
|
+
"rolldown": "^1.0.2",
|
|
69
|
+
"siginfo": "^2.0.0",
|
|
70
|
+
"source-map-js": "^1.2.1",
|
|
71
|
+
"stackback": "^0.0.2",
|
|
72
|
+
"std-env": "^4.1.0",
|
|
73
|
+
"tinybench": "^2.9.0",
|
|
74
|
+
"tinyexec": "^1.2.2",
|
|
75
|
+
"tinyglobby": "^0.2.16",
|
|
76
|
+
"tinyrainbow": "^3.1.0",
|
|
77
|
+
"undici-types": "^7.16.0",
|
|
78
|
+
"vite": "^8.0.14",
|
|
79
|
+
"why-is-node-running": "^2.3.0"
|
|
80
|
+
},
|
|
81
|
+
"keywords": [],
|
|
82
|
+
"author": ""
|
|
83
|
+
}
|