oneclickpush 0.1.0 → 0.2.0

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 CHANGED
@@ -9,7 +9,7 @@ in one command.
9
9
  [complicated](https://github.com/tschaub/gh-pages/tree/main/lib),
10
10
  [buggy](https://github.com/tschaub/gh-pages/issues), and
11
11
  [unmaintained](https://github.com/tschaub/gh-pages/issues/657#issue-4979248775); `oneclickpush` is
12
- none of these, has zero dependencies and only uses Node native functions + `git`.
12
+ none of these, has zero dependencies and only uses built-in Node APIs + `git`.
13
13
 
14
14
  ## Quick Start
15
15
 
@@ -32,7 +32,9 @@ Options:
32
32
  -e, --exclude <glob> Paths to leave out, relative to dist (default: "**/.DS_Store")
33
33
  -b, --branch <name> Target branch (default: "gh-pages")
34
34
  -m, --message <msg> Commit message (default: "Updates")
35
- -f, --force Force push with no history
35
+ -r, --remote <name|url> Remote name or repo URL to publish to (default: "origin")
36
+ -a, --author <author> Commit author, as "Name <email>"
37
+ -f, --force Force push new commit with no history
36
38
  -n, --dry-run Show what would change, without committing or pushing
37
39
  -v, --verbose Log git commands
38
40
  -V, --version Print version
@@ -55,6 +57,13 @@ npx oneclickpush -d public -b release -m "deploy: v1.0.0"
55
57
  # Exclude source maps and test files
56
58
  npx oneclickpush -d dist -e "**/*.map" -e "**/tests/**"
57
59
 
60
+ # Deploy to a different remote or repository URL
61
+ npx oneclickpush -d dist -r upstream
62
+ npx oneclickpush -d dist -r git@github.com:user/repo.git
63
+
64
+ # Set commit author (useful in CI environments)
65
+ npx oneclickpush -d dist -a "Deploy Bot <bot@example.com>"
66
+
58
67
  # Force push fresh history
59
68
  npx oneclickpush -d dist --force
60
69
  ```
@@ -76,6 +85,8 @@ const changes = await publish({
76
85
  exclude: ['**/.DS_Store', '**/*.map'],
77
86
  branch: 'gh-pages',
78
87
  message: 'Deploy updates',
88
+ remote: 'origin',
89
+ author: 'Deploy Bot <bot@example.com>',
79
90
  force: false,
80
91
  dryRun: false,
81
92
  verbose: false
@@ -96,19 +107,22 @@ if (changes) {
96
107
  | `exclude` | `string[]` | `["**/.DS_Store"]` | Paths to exclude relative to `dist` |
97
108
  | `branch` | `string` | `"gh-pages"` | Target git branch |
98
109
  | `message` | `string` | `"Updates"` | Git commit message |
110
+ | `remote` | `string` | `"origin"` | Remote name or repository URL/path to publish to |
111
+ | `author` | `string` | — | Commit author and committer, as `"Name <email>"` |
99
112
  | `force` | `boolean` | `false` | Force push fresh commit with no previous history |
100
113
  | `dryRun` | `boolean` | `false` | Return staged changes without committing or pushing |
101
114
  | `verbose` | `boolean` | `false` | Log executed git commands |
102
115
 
103
116
  ## How It Works
104
117
 
105
- 1. Resolves the remote origin repository URL from local git configuration.
118
+ 1. Resolves the target repository URL from git remote configuration or uses the provided remote
119
+ URL/path directly.
106
120
  2. Creates an isolated temporary directory and fetches the target branch (or initializes it if it
107
121
  doesn't exist).
108
- 3. Clears the target branch index and stages the contents of `dist` (less `exclude` patterns)
122
+ 3. Clears the target branch index and stages the contents of `dist` (minus `exclude` patterns)
109
123
  directly via Git `work-tree`.
110
124
  4. Checks for differences (`git diff --cached --name-status`). If there are changes, commits and
111
- pushes back to `origin`.
125
+ pushes back to the remote repository.
112
126
  5. Cleans up the temporary directory.
113
127
 
114
128
  > [!WARNING]
@@ -116,10 +130,12 @@ if (changes) {
116
130
 
117
131
  ### Notes
118
132
 
119
- - Dot files included by default; use `-e` to exclude.
133
+ - Dotfiles (except `.DS_Store`) are included by default; use `-e` to exclude.
120
134
  - Contents in target branch are emptied and replaced with the contents of `dist` (WYSIWYG).
121
135
  - If `.nojekyll` or `CNAME` files are required, add them to `dist`; use a post-build step if needed.
122
136
  - If the target branch does not exist, it will be created.
137
+ - When `--remote` is a repository URL or path, running from within an existing git repository is not
138
+ required.
123
139
 
124
140
  ## License
125
141
 
package/cli.js CHANGED
@@ -5,7 +5,7 @@ import { publish } from './index.js'
5
5
 
6
6
  const HELP = `Usage: oneclickpush [options]
7
7
 
8
- Replace the contents of a remote git branch with that of a
8
+ Replace the contents of a remote git branch with that of a
9
9
  local directory.
10
10
 
11
11
  Options:
@@ -14,15 +14,18 @@ Options:
14
14
  (default: "**/.DS_Store")
15
15
  -b, --branch <name> Target branch (default: "gh-pages")
16
16
  -m, --message <msg> Commit message (default: "Updates")
17
- -f, --force Force push with no history
18
- -n, --dry-run Show what would change, without
17
+ -r, --remote <name|url> Remote name or repo URL to publish to
18
+ (default: "origin")
19
+ -a, --author <author> Commit author, as "Name <email>"
20
+ -f, --force Force push new commit with no history
21
+ -n, --dry-run Show what would change, without
19
22
  committing or pushing
20
23
  -v, --verbose Log git commands
21
24
  -V, --version Print version
22
25
  -h, --help Show this help
23
26
 
24
- --exclude takes glob patterns (*, **, ?) and can be repeated.
25
- Patterns are matched against the full path, so use **/name to
27
+ --exclude takes glob patterns (*, **, ?) and can be repeated.
28
+ Patterns are matched against the full path, so use **/name to
26
29
  match at any depth.`
27
30
 
28
31
  const { values: v } = parseArgs({
@@ -30,8 +33,10 @@ const { values: v } = parseArgs({
30
33
  dist: { type: 'string', short: 'd', default: '.' },
31
34
  exclude: { type: 'string', short: 'e', multiple: true, default: ['**/.DS_Store'] },
32
35
  branch: { type: 'string', short: 'b', default: 'gh-pages' },
33
- force: { type: 'boolean', short: 'f', default: false },
34
36
  message: { type: 'string', short: 'm', default: 'Updates' },
37
+ remote: { type: 'string', short: 'r', default: 'origin' },
38
+ author: { type: 'string', short: 'a' },
39
+ force: { type: 'boolean', short: 'f', default: false },
35
40
  'dry-run': { type: 'boolean', short: 'n', default: false },
36
41
  verbose: { type: 'boolean', short: 'v', default: false },
37
42
  version: { type: 'boolean', short: 'V', default: false },
package/index.d.ts CHANGED
@@ -16,7 +16,15 @@ export type OneclickpushOptions = {
16
16
  */
17
17
  message?: string
18
18
  /**
19
- * Force push with no history
19
+ * Remote name of the current repo, or a repository URL or path
20
+ */
21
+ remote?: string
22
+ /**
23
+ * Commit author and committer as "Name <email>"
24
+ */
25
+ author?: string
26
+ /**
27
+ * Force push new commit with no history
20
28
  */
21
29
  force?: boolean
22
30
  /**
@@ -34,7 +42,9 @@ export type OneclickpushOptions = {
34
42
  * @prop {string[]} [exclude] Paths to leave out, relative to dist (default: `**\/.DS_Store`)
35
43
  * @prop {string} [branch="gh-pages"] Target branch
36
44
  * @prop {string} [message="Updates"] Commit message
37
- * @prop {boolean} [force=false] Force push with no history
45
+ * @prop {string} [remote="origin"] Remote name of the current repo, or a repository URL or path
46
+ * @prop {string} [author] Commit author and committer as "Name <email>"
47
+ * @prop {boolean} [force=false] Force push new commit with no history
38
48
  * @prop {boolean} [dryRun=false] Show what would change, without committing or pushing
39
49
  * @prop {boolean} [verbose=false] Log git commands
40
50
  */
@@ -49,6 +59,8 @@ export declare function publish({
49
59
  exclude,
50
60
  branch,
51
61
  message,
62
+ remote,
63
+ author,
52
64
  force,
53
65
  dryRun,
54
66
  verbose
package/index.js CHANGED
@@ -10,7 +10,9 @@ import { join, resolve } from 'node:path'
10
10
  * @prop {string[]} [exclude] Paths to leave out, relative to dist (default: `**\/.DS_Store`)
11
11
  * @prop {string} [branch="gh-pages"] Target branch
12
12
  * @prop {string} [message="Updates"] Commit message
13
- * @prop {boolean} [force=false] Force push with no history
13
+ * @prop {string} [remote="origin"] Remote name of the current repo, or a repository URL or path
14
+ * @prop {string} [author] Commit author and committer as "Name <email>"
15
+ * @prop {boolean} [force=false] Force push new commit with no history
14
16
  * @prop {boolean} [dryRun=false] Show what would change, without committing or pushing
15
17
  * @prop {boolean} [verbose=false] Log git commands
16
18
  */
@@ -26,6 +28,8 @@ export async function publish({
26
28
  exclude = ['**/.DS_Store'],
27
29
  branch = 'gh-pages',
28
30
  message = 'Updates',
31
+ remote = 'origin',
32
+ author,
29
33
  force = false,
30
34
  dryRun = false,
31
35
  verbose = false
@@ -33,13 +37,34 @@ export async function publish({
33
37
  dist = resolve(dist)
34
38
  const run = promisify(execFile)
35
39
 
36
- /** @type {(args: string[], cwd?: string) => Promise<string>} */
37
- const git = async (args, cwd) => {
40
+ // Env vars beat git config, so this also overrides any identity already set in CI
41
+ /** @type {Record<string, string>} */
42
+ let identity = {}
43
+ if (author) {
44
+ const m = /^(.+?)\s*<(.+)>$/.exec(author)
45
+ if (!m) throw new Error('--author must look like "Name <email>"')
46
+ identity = {
47
+ GIT_AUTHOR_NAME: m[1],
48
+ GIT_AUTHOR_EMAIL: m[2],
49
+ GIT_COMMITTER_NAME: m[1],
50
+ GIT_COMMITTER_EMAIL: m[2]
51
+ }
52
+ }
53
+
54
+ /** @type {(args: string[], cwd?: string, env?: Record<string, string>) => Promise<string>} */
55
+ const git = async (args, cwd, env) => {
38
56
  if (verbose) console.error(`$ git ${args.join(' ')}`)
39
- return (await run('git', args, { cwd })).stdout.trim()
57
+ return (await run('git', args, { cwd, env: { ...process.env, ...env } })).stdout.trim()
58
+ }
59
+
60
+ // A remote name resolves to its url; anything else is used as a url or path as-is
61
+ let url = remote
62
+ try {
63
+ url = await git(['remote', 'get-url', remote])
64
+ } catch {
65
+ if (/^[\w-]+$/.test(remote)) throw new Error(`Remote "${remote}" not found`)
40
66
  }
41
67
 
42
- const url = await git(['config', '--get', 'remote.origin.url'])
43
68
  const tmp = await mkdtemp(join(tmpdir(), 'oneclickpush-'))
44
69
 
45
70
  try {
@@ -77,7 +102,7 @@ export async function publish({
77
102
  const status = await git(['diff', '--cached', '--name-status'], tmp)
78
103
  if (!status || dryRun) return status
79
104
 
80
- await git(['commit', '-q', '-m', message], tmp)
105
+ await git(['commit', '-q', '-m', message], tmp, identity)
81
106
  await git(['push', '-q', ...(force ? ['--force'] : []), 'origin', branch], tmp)
82
107
  return status
83
108
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oneclickpush",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "One-click GitHub Pages deployer",
5
5
  "author": "Jason Lee <jason@zerodevx.com>",
6
6
  "scripts": {