@micazoyolli/foundation 0.4.0 → 0.4.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.
|
@@ -8,7 +8,7 @@ const main = async () => {
|
|
|
8
8
|
console.log('Repository left unchanged.');
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
console.log(
|
|
11
|
+
console.log(`${result.initialDeployment ? 'Created' : 'Published'} ${result.commit} to ${result.remote}/${result.deploymentBranch}.`);
|
|
12
12
|
console.log('Repository left clean on main.');
|
|
13
13
|
};
|
|
14
14
|
main().catch((error) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cp, mkdtemp, readdir, rm, stat } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import { basename, join, resolve } from 'node:path';
|
|
3
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
const DEFAULT_BUILD_DIR = 'dist';
|
|
6
6
|
const DEFAULT_DEPLOYMENT_BRANCH = 'gh-pages';
|
|
@@ -51,6 +51,27 @@ const getRemoteTrackingRef = (remote, branch) => `refs/remotes/${remote}/${branc
|
|
|
51
51
|
const fetchRemoteBranch = (repositoryRoot, remote, branch) => {
|
|
52
52
|
runGit(['fetch', remote, `+${branch}:${getRemoteTrackingRef(remote, branch)}`], repositoryRoot);
|
|
53
53
|
};
|
|
54
|
+
const remoteBranchExists = (repositoryRoot, remote, branch) => {
|
|
55
|
+
const result = spawnSync('git', ['ls-remote', '--exit-code', '--heads', remote, branch], {
|
|
56
|
+
cwd: repositoryRoot,
|
|
57
|
+
encoding: 'utf8',
|
|
58
|
+
env: {
|
|
59
|
+
...process.env,
|
|
60
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
if (result.error) {
|
|
64
|
+
throw new GithubPagesDeployError(`Failed to check ${remote}/${branch}: ${result.error.message}`);
|
|
65
|
+
}
|
|
66
|
+
if (result.status === 0) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (result.status === 2) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const details = (result.stderr || result.stdout || '').trim();
|
|
73
|
+
throw new GithubPagesDeployError(`Git command failed: git ls-remote --exit-code --heads ${remote} ${branch}${details ? `\n${details}` : ''}`);
|
|
74
|
+
};
|
|
54
75
|
const getRepositoryRoot = (cwd) => {
|
|
55
76
|
try {
|
|
56
77
|
return runGit(['rev-parse', '--show-toplevel'], cwd).stdout;
|
|
@@ -59,33 +80,42 @@ const getRepositoryRoot = (cwd) => {
|
|
|
59
80
|
throw new GithubPagesDeployError('Current directory must be inside a Git repository.');
|
|
60
81
|
}
|
|
61
82
|
};
|
|
83
|
+
const toGitPath = (path) => path.split(sep).join('/');
|
|
84
|
+
const getBuildDirectoryPathspec = (repositoryRoot, buildDirectory) => {
|
|
85
|
+
const relativeBuildDirectory = relative(repositoryRoot, buildDirectory);
|
|
86
|
+
if (!relativeBuildDirectory || relativeBuildDirectory === '..' || relativeBuildDirectory.startsWith(`..${sep}`) || isAbsolute(relativeBuildDirectory)) {
|
|
87
|
+
throw new GithubPagesDeployError(`Build directory must be inside the repository: ${buildDirectory}`);
|
|
88
|
+
}
|
|
89
|
+
return toGitPath(relativeBuildDirectory);
|
|
90
|
+
};
|
|
91
|
+
const assertBuildDirectoryIsUntracked = (repositoryRoot, buildDirectoryPathspec, sourceBranch) => {
|
|
92
|
+
const trackedFiles = runGit(['ls-files', '--', buildDirectoryPathspec], repositoryRoot).stdout;
|
|
93
|
+
if (trackedFiles) {
|
|
94
|
+
throw new GithubPagesDeployError([
|
|
95
|
+
`Build directory "${buildDirectoryPathspec}" must not be tracked by Git.`,
|
|
96
|
+
`Remove it from ${sourceBranch} with: git rm -r --cached ${buildDirectoryPathspec}`,
|
|
97
|
+
`Keep "${buildDirectoryPathspec}/" ignored in .gitignore before deploying.`,
|
|
98
|
+
].join('\n'));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
62
101
|
const assertCleanWorkingTree = (repositoryRoot) => {
|
|
63
102
|
const status = runGit(['status', '--porcelain'], repositoryRoot).stdout;
|
|
64
103
|
if (status) {
|
|
65
104
|
throw new GithubPagesDeployError(`Working tree must be clean before deploying.\n${status}`);
|
|
66
105
|
}
|
|
67
106
|
};
|
|
68
|
-
const assertSyncedBranch = (repositoryRoot, sourceBranch, remote
|
|
107
|
+
const assertSyncedBranch = (repositoryRoot, sourceBranch, remote) => {
|
|
69
108
|
const currentBranch = runGit(['branch', '--show-current'], repositoryRoot).stdout;
|
|
70
109
|
if (currentBranch !== sourceBranch) {
|
|
71
110
|
throw new GithubPagesDeployError(`Current branch must be "${sourceBranch}". Found "${currentBranch || 'detached HEAD'}".`);
|
|
72
111
|
}
|
|
73
112
|
fetchRemoteBranch(repositoryRoot, remote, sourceBranch);
|
|
74
|
-
fetchRemoteBranch(repositoryRoot, remote, deploymentBranch);
|
|
75
113
|
const localHead = runGit(['rev-parse', sourceBranch], repositoryRoot).stdout;
|
|
76
114
|
const remoteHead = runGit(['rev-parse', `${remote}/${sourceBranch}`], repositoryRoot).stdout;
|
|
77
115
|
if (localHead !== remoteHead) {
|
|
78
116
|
throw new GithubPagesDeployError(`${sourceBranch} must be synchronized with ${remote}/${sourceBranch} before deploying.`);
|
|
79
117
|
}
|
|
80
118
|
};
|
|
81
|
-
const assertDeploymentBranchExists = (repositoryRoot, deploymentRef) => {
|
|
82
|
-
try {
|
|
83
|
-
runGit(['rev-parse', '--verify', `${deploymentRef}^{commit}`], repositoryRoot);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
throw new GithubPagesDeployError(`Deployment branch "${deploymentRef}" must exist before deploying.`);
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
119
|
const assertBuildDirectory = async (buildDirectory) => {
|
|
90
120
|
let buildStat;
|
|
91
121
|
try {
|
|
@@ -133,15 +163,23 @@ export const deployGithubPages = async (options = {}) => {
|
|
|
133
163
|
assertSafeGitRefPart(resolved.deploymentBranch, 'deployment branch');
|
|
134
164
|
const repositoryRoot = getRepositoryRoot(resolved.cwd);
|
|
135
165
|
const buildDirectory = resolve(repositoryRoot, resolved.buildDir);
|
|
166
|
+
const buildDirectoryPathspec = getBuildDirectoryPathspec(repositoryRoot, buildDirectory);
|
|
136
167
|
const deploymentRef = `${resolved.remote}/${resolved.deploymentBranch}`;
|
|
168
|
+
assertBuildDirectoryIsUntracked(repositoryRoot, buildDirectoryPathspec, resolved.sourceBranch);
|
|
137
169
|
assertCleanWorkingTree(repositoryRoot);
|
|
138
|
-
assertSyncedBranch(repositoryRoot, resolved.sourceBranch, resolved.remote
|
|
139
|
-
|
|
170
|
+
assertSyncedBranch(repositoryRoot, resolved.sourceBranch, resolved.remote);
|
|
171
|
+
const deploymentBranchExists = remoteBranchExists(repositoryRoot, resolved.remote, resolved.deploymentBranch);
|
|
172
|
+
if (deploymentBranchExists) {
|
|
173
|
+
fetchRemoteBranch(repositoryRoot, resolved.remote, resolved.deploymentBranch);
|
|
174
|
+
}
|
|
140
175
|
await assertBuildDirectory(buildDirectory);
|
|
141
176
|
const tempRoot = await mkdtemp(join(tmpdir(), 'micazoyolli-gh-pages-'));
|
|
142
177
|
const worktreePath = join(tempRoot, basename(repositoryRoot));
|
|
143
178
|
try {
|
|
144
|
-
runGit(['worktree', 'add', '--detach', worktreePath, deploymentRef], repositoryRoot);
|
|
179
|
+
runGit(['worktree', 'add', '--detach', worktreePath, deploymentBranchExists ? deploymentRef : resolved.sourceBranch], repositoryRoot);
|
|
180
|
+
if (!deploymentBranchExists) {
|
|
181
|
+
runGit(['checkout', '--orphan', resolved.deploymentBranch], worktreePath);
|
|
182
|
+
}
|
|
145
183
|
await clearWorktree(worktreePath);
|
|
146
184
|
await cp(buildDirectory, worktreePath, {
|
|
147
185
|
force: true,
|
|
@@ -151,6 +189,7 @@ export const deployGithubPages = async (options = {}) => {
|
|
|
151
189
|
if (!deploymentStatus) {
|
|
152
190
|
return {
|
|
153
191
|
deploymentBranch: resolved.deploymentBranch,
|
|
192
|
+
initialDeployment: false,
|
|
154
193
|
pushed: false,
|
|
155
194
|
reason: 'Deployment content is unchanged.',
|
|
156
195
|
remote: resolved.remote,
|
|
@@ -165,6 +204,7 @@ export const deployGithubPages = async (options = {}) => {
|
|
|
165
204
|
return {
|
|
166
205
|
commit,
|
|
167
206
|
deploymentBranch: resolved.deploymentBranch,
|
|
207
|
+
initialDeployment: !deploymentBranchExists,
|
|
168
208
|
pushed: true,
|
|
169
209
|
remote: resolved.remote,
|
|
170
210
|
repositoryRoot,
|