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.
Files changed (83) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +183 -0
  3. package/README.md +93 -0
  4. package/dist/adapters/local-deploy-execution-ports.d.ts +12 -0
  5. package/dist/adapters/local-deploy-execution-ports.js +81 -0
  6. package/dist/adapters/local-environment-probe.d.ts +5 -0
  7. package/dist/adapters/local-environment-probe.js +54 -0
  8. package/dist/adapters/local-init-apply-ports.d.ts +10 -0
  9. package/dist/adapters/local-init-apply-ports.js +47 -0
  10. package/dist/adapters/local-project-inspector.d.ts +4 -0
  11. package/dist/adapters/local-project-inspector.js +184 -0
  12. package/dist/application/contracts/plugins/deploy-mode-plugin.d.ts +20 -0
  13. package/dist/application/contracts/plugins/deploy-mode-plugin.js +1 -0
  14. package/dist/application/contracts/plugins/framework-plugin.d.ts +19 -0
  15. package/dist/application/contracts/plugins/framework-plugin.js +1 -0
  16. package/dist/application/contracts/ports/environment-probe-port.d.ts +4 -0
  17. package/dist/application/contracts/ports/environment-probe-port.js +1 -0
  18. package/dist/application/contracts/ports/project-inspector-port.d.ts +28 -0
  19. package/dist/application/contracts/ports/project-inspector-port.js +1 -0
  20. package/dist/application/contracts/project-context.d.ts +6 -0
  21. package/dist/application/contracts/project-context.js +1 -0
  22. package/dist/application/index.d.ts +11 -0
  23. package/dist/application/index.js +11 -0
  24. package/dist/application/usecases/apply-deploy-plan.d.ts +8 -0
  25. package/dist/application/usecases/apply-deploy-plan.js +100 -0
  26. package/dist/application/usecases/apply-init-plan.d.ts +13 -0
  27. package/dist/application/usecases/apply-init-plan.js +65 -0
  28. package/dist/application/usecases/build-deploy-plan.d.ts +9 -0
  29. package/dist/application/usecases/build-deploy-plan.js +177 -0
  30. package/dist/application/usecases/build-doctor-plan.d.ts +9 -0
  31. package/dist/application/usecases/build-doctor-plan.js +235 -0
  32. package/dist/application/usecases/build-init-plan.d.ts +9 -0
  33. package/dist/application/usecases/build-init-plan.js +140 -0
  34. package/dist/application/usecases/create-project-context.d.ts +8 -0
  35. package/dist/application/usecases/create-project-context.js +168 -0
  36. package/dist/application/usecases/workflow-diagnostics.d.ts +13 -0
  37. package/dist/application/usecases/workflow-diagnostics.js +137 -0
  38. package/dist/core/diagnostics/diagnostic.d.ts +11 -0
  39. package/dist/core/diagnostics/diagnostic.js +1 -0
  40. package/dist/core/entities/command.d.ts +1 -0
  41. package/dist/core/entities/command.js +1 -0
  42. package/dist/core/entities/deploy-mode.d.ts +6 -0
  43. package/dist/core/entities/deploy-mode.js +1 -0
  44. package/dist/core/entities/environment.d.ts +16 -0
  45. package/dist/core/entities/environment.js +1 -0
  46. package/dist/core/entities/framework.d.ts +8 -0
  47. package/dist/core/entities/framework.js +1 -0
  48. package/dist/core/entities/pages-target.d.ts +22 -0
  49. package/dist/core/entities/pages-target.js +1 -0
  50. package/dist/core/entities/project.d.ts +45 -0
  51. package/dist/core/entities/project.js +1 -0
  52. package/dist/core/index.d.ts +13 -0
  53. package/dist/core/index.js +13 -0
  54. package/dist/core/plans/assumption.d.ts +7 -0
  55. package/dist/core/plans/assumption.js +1 -0
  56. package/dist/core/plans/build-plan.d.ts +12 -0
  57. package/dist/core/plans/build-plan.js +1 -0
  58. package/dist/core/plans/command-plan.d.ts +27 -0
  59. package/dist/core/plans/command-plan.js +45 -0
  60. package/dist/core/plans/deploy-plan.d.ts +17 -0
  61. package/dist/core/plans/deploy-plan.js +1 -0
  62. package/dist/core/plans/plan-step.d.ts +59 -0
  63. package/dist/core/plans/plan-step.js +1 -0
  64. package/dist/core/plans/template-plan.d.ts +11 -0
  65. package/dist/core/plans/template-plan.js +1 -0
  66. package/dist/features/deployment-modes/git-pages-deploy-mode-plugin.d.ts +12 -0
  67. package/dist/features/deployment-modes/git-pages-deploy-mode-plugin.js +49 -0
  68. package/dist/features/frameworks/astro-framework-plugin.d.ts +36 -0
  69. package/dist/features/frameworks/astro-framework-plugin.js +97 -0
  70. package/dist/features/frameworks/static-html-framework-plugin.d.ts +36 -0
  71. package/dist/features/frameworks/static-html-framework-plugin.js +94 -0
  72. package/dist/features/frameworks/vite-framework-plugin.d.ts +36 -0
  73. package/dist/features/frameworks/vite-framework-plugin.js +97 -0
  74. package/dist/index.d.ts +2 -0
  75. package/dist/index.js +2 -0
  76. package/dist/interfaces/cli/main.d.ts +2 -0
  77. package/dist/interfaces/cli/main.js +157 -0
  78. package/dist/interfaces/cli/render-command-plan.d.ts +2 -0
  79. package/dist/interfaces/cli/render-command-plan.js +87 -0
  80. package/docs/CANARY.md +117 -0
  81. package/docs/COMPATIBILITY.md +88 -0
  82. package/docs/DEPLOY-SAFETY.md +113 -0
  83. 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,2 @@
1
+ import type { CommandPlan } from "../../core/plans/command-plan.js";
2
+ export declare function renderCommandPlan(plan: CommandPlan): string;
@@ -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
+ }