oneclickpush 0.1.0 → 0.3.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.
Files changed (5) hide show
  1. package/README.md +85 -35
  2. package/cli.js +80 -37
  3. package/index.d.ts +40 -17
  4. package/index.js +63 -24
  5. package/package.json +3 -2
package/README.md CHANGED
@@ -9,32 +9,35 @@ 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
 
16
16
  Deploy your build directory (e.g. `dist`) to the `gh-pages` branch:
17
17
 
18
18
  ```text
19
- $ cd dist
20
- $ npx oneclickpush
19
+ $ npx oneclickpush dist
21
20
  ```
22
21
 
23
22
  ## CLI Usage
24
23
 
25
24
  ```text
26
- Usage: oneclickpush [options]
25
+ Usage: oneclickpush <dir> [options]
27
26
 
28
27
  Replace the contents of a remote git branch with that of a local directory.
29
28
 
29
+ Arguments:
30
+ dir Directory to deploy (required)
31
+
30
32
  Options:
31
- -d, --dist <dir> Directory to publish (default: ".")
32
- -e, --exclude <glob> Paths to leave out, relative to dist (default: "**/.DS_Store")
33
+ -e, --exclude <glob> Paths to leave out, relative to dir (default: "**/.DS_Store")
33
34
  -b, --branch <name> Target branch (default: "gh-pages")
34
35
  -m, --message <msg> Commit message (default: "Updates")
35
- -f, --force Force push with no history
36
+ -r, --remote <name|url> Remote name or repo URL to deploy to (default: "origin")
37
+ -a, --author <author> Commit author, as "Name <email>"
38
+ -f, --force Force push new commit with no history
36
39
  -n, --dry-run Show what would change, without committing or pushing
37
- -v, --verbose Log git commands
40
+ -l, --log-level <level> Output: silent, normal or verbose (default: "normal")
38
41
  -V, --version Print version
39
42
  -h, --help Show this help
40
43
  ```
@@ -43,20 +46,46 @@ Options:
43
46
  > `--exclude` takes glob patterns (`*`, `**`, `?`) and can be repeated. Patterns are matched against
44
47
  > the full path, so use `**/name` to match at any depth.
45
48
 
49
+ ### Log levels
50
+
51
+ | Level | Behavior |
52
+ | --------- | -------------------------------------------------------------------- |
53
+ | `silent` | Prints nothing, not even errors. Check the exit code instead. |
54
+ | `normal` | Prints the result and error messages (plus a spinner in a terminal). |
55
+ | `verbose` | Also logs every git command to stderr and prints full errors. |
56
+
57
+ > [!WARNING]
58
+ > If your remote URL contains a token (e.g. `https://user:TOKEN@github.com/o/r.git`), git error
59
+ > messages shown at `normal` and `verbose` may include it. Use `-l silent` in CI, or keep tokens out
60
+ > of the URL with a credential helper.
61
+
46
62
  ### Examples
47
63
 
48
64
  ```bash
49
65
  # Preview what would be deployed without pushing
50
- npx oneclickpush -d build --dry-run
66
+ npx oneclickpush build --dry-run
51
67
 
52
68
  # Custom branch and commit message
53
- npx oneclickpush -d public -b release -m "deploy: v1.0.0"
69
+ npx oneclickpush public -b release -m "deploy: v1.0.0"
54
70
 
55
71
  # Exclude source maps and test files
56
- npx oneclickpush -d dist -e "**/*.map" -e "**/tests/**"
72
+ npx oneclickpush dist -e "**/*.map" -e "**/tests/**"
73
+
74
+ # Deploy to a different remote or repository URL
75
+ npx oneclickpush dist -r upstream
76
+ npx oneclickpush dist -r git@github.com:user/repo.git
77
+
78
+ # Set commit author (useful in CI environments)
79
+ npx oneclickpush dist -a "Deploy Bot <bot@example.com>"
57
80
 
58
81
  # Force push fresh history
59
- npx oneclickpush -d dist --force
82
+ npx oneclickpush dist --force
83
+
84
+ # Silent output (useful in CI, check the exit code)
85
+ npx oneclickpush dist -l silent
86
+
87
+ # Log every git command
88
+ npx oneclickpush dist -l verbose
60
89
  ```
61
90
 
62
91
  ## Programmatic API
@@ -64,51 +93,68 @@ npx oneclickpush -d dist --force
64
93
  You can also use `oneclickpush` as an ES module:
65
94
 
66
95
  ```bash
67
- # Install the library
96
+ # Install
68
97
  npm i oneclickpush
69
98
  ```
70
99
 
71
100
  ```javascript
72
- import { publish } from 'oneclickpush'
101
+ import { deploy } from 'oneclickpush'
73
102
 
74
- const changes = await publish({
75
- dist: 'dist',
103
+ const changes = await deploy({
104
+ dir: 'dist',
76
105
  exclude: ['**/.DS_Store', '**/*.map'],
77
106
  branch: 'gh-pages',
78
107
  message: 'Deploy updates',
108
+ remote: 'origin',
109
+ author: 'Deploy Bot <bot@example.com>',
79
110
  force: false,
80
111
  dryRun: false,
81
- verbose: false
112
+ logLevel: 'normal'
82
113
  })
83
114
 
84
115
  if (changes) {
85
- console.log('Published changes:\n' + changes)
116
+ console.log('Deployed changes:\n' + changes)
86
117
  } else {
87
- console.log('Nothing to publish')
118
+ console.log('Nothing to deploy')
88
119
  }
89
120
  ```
90
121
 
122
+ The default values are also exported as `DEFAULTS`:
123
+
124
+ ```javascript
125
+ import { DEFAULTS } from 'oneclickpush'
126
+
127
+ console.log(DEFAULTS.branch) // "gh-pages"
128
+ ```
129
+
91
130
  ### Options
92
131
 
93
- | Option | Type | Default | Description |
94
- | --------- | ---------- | ------------------ | --------------------------------------------------- |
95
- | `dist` | `string` | `"."` | Directory to publish |
96
- | `exclude` | `string[]` | `["**/.DS_Store"]` | Paths to exclude relative to `dist` |
97
- | `branch` | `string` | `"gh-pages"` | Target git branch |
98
- | `message` | `string` | `"Updates"` | Git commit message |
99
- | `force` | `boolean` | `false` | Force push fresh commit with no previous history |
100
- | `dryRun` | `boolean` | `false` | Return staged changes without committing or pushing |
101
- | `verbose` | `boolean` | `false` | Log executed git commands |
132
+ | Option | Type | Default | Description |
133
+ | ---------- | ----------------------------------- | ------------------ | --------------------------------------------------- |
134
+ | `dir` | `string` | - (required) | Directory to deploy |
135
+ | `exclude` | `string[]` | `["**/.DS_Store"]` | Paths to exclude relative to `dir` |
136
+ | `branch` | `string` | `"gh-pages"` | Target git branch |
137
+ | `message` | `string` | `"Updates"` | Git commit message |
138
+ | `remote` | `string` | `"origin"` | Remote name or repository URL/path to deploy to |
139
+ | `author` | `string` | - | Commit author and committer, as `"Name <email>"` |
140
+ | `force` | `boolean` | `false` | Force push fresh commit with no previous history |
141
+ | `dryRun` | `boolean` | `false` | Return staged changes without committing or pushing |
142
+ | `logLevel` | `"silent" \| "normal" \| "verbose"` | `"normal"` | Only `"verbose"` has an effect: logs git commands |
143
+
144
+ > [!NOTE]
145
+ > The library never prints anything except at `"verbose"`. `"silent"` and `"normal"` only differ in
146
+ > the CLI.
102
147
 
103
148
  ## How It Works
104
149
 
105
- 1. Resolves the remote origin repository URL from local git configuration.
150
+ 1. Resolves the target repository URL from git remote configuration or uses the provided remote
151
+ URL/path directly.
106
152
  2. Creates an isolated temporary directory and fetches the target branch (or initializes it if it
107
153
  doesn't exist).
108
- 3. Clears the target branch index and stages the contents of `dist` (less `exclude` patterns)
154
+ 3. Clears the target branch index and stages the contents of `dir` (minus `exclude` patterns)
109
155
  directly via Git `work-tree`.
110
156
  4. Checks for differences (`git diff --cached --name-status`). If there are changes, commits and
111
- pushes back to `origin`.
157
+ pushes back to the remote repository.
112
158
  5. Cleans up the temporary directory.
113
159
 
114
160
  > [!WARNING]
@@ -116,10 +162,14 @@ if (changes) {
116
162
 
117
163
  ### Notes
118
164
 
119
- - Dot files included by default; use `-e` to exclude.
120
- - Contents in target branch are emptied and replaced with the contents of `dist` (WYSIWYG).
121
- - If `.nojekyll` or `CNAME` files are required, add them to `dist`; use a post-build step if needed.
122
- - If the target branch does not exist, it will be created.
165
+ - The target branch is fully replaced with the contents of `dir` (WYSIWYG), and it's created if it
166
+ doesn't exist yet. If you need files like `.nojekyll` or `CNAME`, put them in `dir`, adding a
167
+ post-build step if your build tool wipes the directory.
168
+ - Dotfiles are included by default, except `.DS_Store`. Use `-e` to exclude more, but note that it
169
+ replaces the default list, so repeat `-e "**/.DS_Store"` if you still want that file excluded.
170
+ - When `--remote` is a repository URL or path, you don't need to run from inside a git repository.
171
+ - In dry-run output, identical content moved to a new path appears as a rename (`R100`).
172
+ - A directory starting with `-` must come after `--`, e.g. `oneclickpush -- -weird-dir`.
123
173
 
124
174
  ## License
125
175
 
package/cli.js CHANGED
@@ -1,43 +1,67 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from 'node:util'
3
3
  import { readFileSync } from 'node:fs'
4
- import { publish } from './index.js'
4
+ import { deploy, DEFAULTS } from './index.js'
5
5
 
6
- const HELP = `Usage: oneclickpush [options]
6
+ const HELP = `Usage: oneclickpush <dir> [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
+ Arguments:
12
+ dir Directory to deploy (required)
13
+
11
14
  Options:
12
- -d, --dist <dir> Directory to publish (default: ".")
13
- -e, --exclude <glob> Paths to leave out, relative to dist
14
- (default: "**/.DS_Store")
15
- -b, --branch <name> Target branch (default: "gh-pages")
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
15
+ -e, --exclude <glob> Paths to leave out, relative to dir
16
+ (default: "${DEFAULTS.exclude.join(', ')}")
17
+ -b, --branch <name> Target branch (default: "${DEFAULTS.branch}")
18
+ -m, --message <msg> Commit message (default: "${DEFAULTS.message}")
19
+ -r, --remote <name|url> Remote name or repo URL to deploy to
20
+ (default: "${DEFAULTS.remote}")
21
+ -a, --author <author> Commit author, as "Name <email>"
22
+ -f, --force Force push new commit with no history
23
+ -n, --dry-run Show what would change, without
19
24
  committing or pushing
20
- -v, --verbose Log git commands
25
+ -l, --log-level <level> Output: silent, normal or verbose
26
+ (default: "${DEFAULTS.logLevel}")
21
27
  -V, --version Print version
22
28
  -h, --help Show this help
23
29
 
24
- --exclude takes glob patterns (*, **, ?) and can be repeated.
25
- Patterns are matched against the full path, so use **/name to
26
- match at any depth.`
30
+ --exclude takes glob patterns (*, **, ?) and can be repeated.
31
+ Patterns are matched against the full path, so use **/name to
32
+ match at any depth.
33
+
34
+ Log levels: "silent" prints nothing (check the exit code),
35
+ "normal" prints the result and errors, "verbose" also logs git
36
+ commands and full errors.`
37
+
38
+ const LEVELS = ['silent', 'normal', 'verbose']
27
39
 
28
- const { values: v } = parseArgs({
29
- options: {
30
- dist: { type: 'string', short: 'd', default: '.' },
31
- exclude: { type: 'string', short: 'e', multiple: true, default: ['**/.DS_Store'] },
32
- branch: { type: 'string', short: 'b', default: 'gh-pages' },
33
- force: { type: 'boolean', short: 'f', default: false },
34
- message: { type: 'string', short: 'm', default: 'Updates' },
35
- 'dry-run': { type: 'boolean', short: 'n', default: false },
36
- verbose: { type: 'boolean', short: 'v', default: false },
37
- version: { type: 'boolean', short: 'V', default: false },
38
- help: { type: 'boolean', short: 'h', default: false }
40
+ const { values: v, positionals } = (() => {
41
+ try {
42
+ return parseArgs({
43
+ allowPositionals: true,
44
+ options: {
45
+ exclude: { type: 'string', short: 'e', multiple: true, default: DEFAULTS.exclude },
46
+ branch: { type: 'string', short: 'b', default: DEFAULTS.branch },
47
+ message: { type: 'string', short: 'm', default: DEFAULTS.message },
48
+ remote: { type: 'string', short: 'r', default: DEFAULTS.remote },
49
+ author: { type: 'string', short: 'a' },
50
+ force: { type: 'boolean', short: 'f', default: DEFAULTS.force },
51
+ 'dry-run': { type: 'boolean', short: 'n', default: DEFAULTS.dryRun },
52
+ 'log-level': { type: 'string', short: 'l', default: DEFAULTS.logLevel },
53
+ version: { type: 'boolean', short: 'V', default: false },
54
+ help: { type: 'boolean', short: 'h', default: false }
55
+ }
56
+ })
57
+ } catch (/** @type {any} */ e) {
58
+ if (!e.code?.startsWith('ERR_PARSE_ARGS_')) throw e
59
+ // Keep only the first sentence, Node appends a long "-- " hint to some messages
60
+ const msg = e.message.split('. ')[0].replace(/\.$/, '')
61
+ console.error(`${msg}, run "oneclickpush --help" for usage`)
62
+ process.exit(1)
39
63
  }
40
- })
64
+ })()
41
65
 
42
66
  if (v.help) {
43
67
  console.log(HELP)
@@ -50,27 +74,46 @@ if (v.version) {
50
74
  process.exit(0)
51
75
  }
52
76
 
53
- // Dead simple spinner (skipped when verbose or piped)
77
+ if (positionals.length !== 1) {
78
+ console.error(
79
+ positionals.length === 0
80
+ ? 'Missing required argument <dir>, run "oneclickpush --help" for usage'
81
+ : 'Expected exactly one directory, run "oneclickpush --help" for usage'
82
+ )
83
+ process.exit(1)
84
+ }
85
+ const dir = positionals[0]
86
+
87
+ const level = /** @type {import('./index.js').OcpOptionsLogLevel} */ (v['log-level'])
88
+ if (!LEVELS.includes(level)) {
89
+ console.error(`Invalid --log-level "${level}", expected one of: ${LEVELS.join(', ')}`)
90
+ process.exit(1)
91
+ }
92
+
93
+ // Dead simple spinner (normal level, TTY only)
54
94
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
55
95
  let i = 0
56
96
  const spin =
57
- process.stderr.isTTY && !v.verbose
97
+ process.stderr.isTTY && level === 'normal'
58
98
  ? setInterval(() => process.stderr.write(`\r${frames[i++ % frames.length]}`), 100)
59
99
  : null
60
100
  const stop = () => spin && (clearInterval(spin), process.stderr.write('\r \r'))
61
101
 
62
102
  try {
63
- const changes = await publish({ ...v, dryRun: v['dry-run'] })
103
+ const changes = await deploy({ ...v, dir, logLevel: level, dryRun: v['dry-run'] })
64
104
  stop()
65
- console.log(
66
- !changes
67
- ? 'Nothing to publish'
68
- : v['dry-run']
69
- ? `Dry run, would publish:\n${changes}`
70
- : 'Published'
71
- )
105
+ if (level !== 'silent') {
106
+ console.log(
107
+ !changes
108
+ ? 'Nothing to deploy'
109
+ : v['dry-run']
110
+ ? `Dry run, would deploy:\n${changes}`
111
+ : 'Deployed!'
112
+ )
113
+ }
72
114
  } catch (/** @type {any} */ e) {
73
115
  stop()
74
- console.error(v.verbose ? e : e.message)
116
+ if (level === 'verbose') console.error(e)
117
+ else if (level === 'normal') console.error(e.message)
75
118
  process.exit(1)
76
119
  }
package/index.d.ts CHANGED
@@ -1,10 +1,21 @@
1
- export type OneclickpushOptions = {
1
+ export type OcpOptionsLogLevel = 'silent' | 'normal' | 'verbose'
2
+ /** @typedef {'silent' | 'normal' | 'verbose'} OcpOptionsLogLevel */
3
+ export declare const DEFAULTS: {
4
+ exclude: string[]
5
+ branch: string
6
+ message: string
7
+ remote: string
8
+ force: boolean
9
+ dryRun: boolean
10
+ logLevel: OcpOptionsLogLevel
11
+ }
12
+ export type OcpOptions = {
2
13
  /**
3
- * Directory to publish
14
+ * Directory to deploy (required)
4
15
  */
5
- dist?: string
16
+ dir: string
6
17
  /**
7
- * Paths to leave out, relative to dist (default: `**\/.DS_Store`)
18
+ * Paths to leave out, relative to dir (default: `**\/.DS_Store`)
8
19
  */
9
20
  exclude?: string[]
10
21
  /**
@@ -16,7 +27,15 @@ export type OneclickpushOptions = {
16
27
  */
17
28
  message?: string
18
29
  /**
19
- * Force push with no history
30
+ * Remote name of the current repo, or a repository URL or path
31
+ */
32
+ remote?: string
33
+ /**
34
+ * Commit author and committer as "Name <email>"
35
+ */
36
+ author?: string
37
+ /**
38
+ * Force push new commit with no history
20
39
  */
21
40
  force?: boolean
22
41
  /**
@@ -24,32 +43,36 @@ export type OneclickpushOptions = {
24
43
  */
25
44
  dryRun?: boolean
26
45
  /**
27
- * Log git commands
46
+ * Only "verbose" logs (git commands, to stderr)
28
47
  */
29
- verbose?: boolean
48
+ logLevel?: OcpOptionsLogLevel
30
49
  }
31
50
  /**
32
- * @typedef {object} OneclickpushOptions
33
- * @prop {string} [dist="."] Directory to publish
34
- * @prop {string[]} [exclude] Paths to leave out, relative to dist (default: `**\/.DS_Store`)
51
+ * @typedef {object} OcpOptions
52
+ * @prop {string} dir Directory to deploy (required)
53
+ * @prop {string[]} [exclude] Paths to leave out, relative to dir (default: `**\/.DS_Store`)
35
54
  * @prop {string} [branch="gh-pages"] Target branch
36
55
  * @prop {string} [message="Updates"] Commit message
37
- * @prop {boolean} [force=false] Force push with no history
56
+ * @prop {string} [remote="origin"] Remote name of the current repo, or a repository URL or path
57
+ * @prop {string} [author] Commit author and committer as "Name <email>"
58
+ * @prop {boolean} [force=false] Force push new commit with no history
38
59
  * @prop {boolean} [dryRun=false] Show what would change, without committing or pushing
39
- * @prop {boolean} [verbose=false] Log git commands
60
+ * @prop {OcpOptionsLogLevel} [logLevel="normal"] Only "verbose" logs (git commands, to stderr)
40
61
  */
41
62
  /**
42
63
  * Replace the contents of a git branch with the contents of a directory.
43
64
  * Returns the staged changes as `git diff --name-status` text or "" if nothing changed.
44
- * @param {OneclickpushOptions} options
65
+ * @param {OcpOptions} options
45
66
  * @returns {Promise<string>}
46
67
  */
47
- export declare function publish({
48
- dist,
68
+ export declare function deploy({
69
+ dir,
49
70
  exclude,
50
71
  branch,
51
72
  message,
73
+ remote,
74
+ author,
52
75
  force,
53
76
  dryRun,
54
- verbose
55
- }: OneclickpushOptions): Promise<string>
77
+ logLevel
78
+ }: OcpOptions): Promise<string>
package/index.js CHANGED
@@ -4,42 +4,81 @@ import { mkdtemp, rm } from 'node:fs/promises'
4
4
  import { tmpdir } from 'node:os'
5
5
  import { join, resolve } from 'node:path'
6
6
 
7
+ /** @typedef {'silent' | 'normal' | 'verbose'} OcpOptionsLogLevel */
8
+
9
+ export const DEFAULTS = {
10
+ exclude: ['**/.DS_Store'],
11
+ branch: 'gh-pages',
12
+ message: 'Updates',
13
+ remote: 'origin',
14
+ force: false,
15
+ dryRun: false,
16
+ logLevel: /** @type {OcpOptionsLogLevel} */ ('normal')
17
+ }
18
+
7
19
  /**
8
- * @typedef {object} OneclickpushOptions
9
- * @prop {string} [dist="."] Directory to publish
10
- * @prop {string[]} [exclude] Paths to leave out, relative to dist (default: `**\/.DS_Store`)
20
+ * @typedef {object} OcpOptions
21
+ * @prop {string} dir Directory to deploy (required)
22
+ * @prop {string[]} [exclude] Paths to leave out, relative to dir (default: `**\/.DS_Store`)
11
23
  * @prop {string} [branch="gh-pages"] Target branch
12
24
  * @prop {string} [message="Updates"] Commit message
13
- * @prop {boolean} [force=false] Force push with no history
25
+ * @prop {string} [remote="origin"] Remote name of the current repo, or a repository URL or path
26
+ * @prop {string} [author] Commit author and committer as "Name <email>"
27
+ * @prop {boolean} [force=false] Force push new commit with no history
14
28
  * @prop {boolean} [dryRun=false] Show what would change, without committing or pushing
15
- * @prop {boolean} [verbose=false] Log git commands
29
+ * @prop {OcpOptionsLogLevel} [logLevel="normal"] Only "verbose" logs (git commands, to stderr)
16
30
  */
17
31
 
18
32
  /**
19
33
  * Replace the contents of a git branch with the contents of a directory.
20
34
  * Returns the staged changes as `git diff --name-status` text or "" if nothing changed.
21
- * @param {OneclickpushOptions} options
35
+ * @param {OcpOptions} options
22
36
  * @returns {Promise<string>}
23
37
  */
24
- export async function publish({
25
- dist = '.',
26
- exclude = ['**/.DS_Store'],
27
- branch = 'gh-pages',
28
- message = 'Updates',
29
- force = false,
30
- dryRun = false,
31
- verbose = false
38
+ export async function deploy({
39
+ dir,
40
+ exclude = DEFAULTS.exclude,
41
+ branch = DEFAULTS.branch,
42
+ message = DEFAULTS.message,
43
+ remote = DEFAULTS.remote,
44
+ author,
45
+ force = DEFAULTS.force,
46
+ dryRun = DEFAULTS.dryRun,
47
+ logLevel = DEFAULTS.logLevel
32
48
  }) {
33
- dist = resolve(dist)
49
+ if (!dir) throw new Error('dir is required')
50
+ dir = resolve(dir)
34
51
  const run = promisify(execFile)
35
52
 
36
- /** @type {(args: string[], cwd?: string) => Promise<string>} */
37
- const git = async (args, cwd) => {
38
- if (verbose) console.error(`$ git ${args.join(' ')}`)
39
- return (await run('git', args, { cwd })).stdout.trim()
53
+ // Env vars beat git config, so this also overrides any identity already set in CI
54
+ /** @type {Record<string, string>} */
55
+ let identity = {}
56
+ if (author) {
57
+ const m = /^(.+?)\s*<(.+)>$/.exec(author)
58
+ if (!m) throw new Error('--author must look like "Name <email>"')
59
+ identity = {
60
+ GIT_AUTHOR_NAME: m[1],
61
+ GIT_AUTHOR_EMAIL: m[2],
62
+ GIT_COMMITTER_NAME: m[1],
63
+ GIT_COMMITTER_EMAIL: m[2]
64
+ }
65
+ }
66
+
67
+ /** @type {(args: string[], cwd?: string, env?: Record<string, string>) => Promise<string>} */
68
+ const git = async (args, cwd, env) => {
69
+ if (logLevel === 'verbose') console.error(`$ git ${args.join(' ')}`)
70
+ const { stdout } = await run('git', args, { cwd, env: { ...process.env, ...env } })
71
+ return stdout.trim()
72
+ }
73
+
74
+ // A remote name resolves to its url; anything else is used as a url or path as-is
75
+ let url = remote
76
+ try {
77
+ url = await git(['remote', 'get-url', remote])
78
+ } catch {
79
+ if (/^[\w-]+$/.test(remote)) throw new Error(`Remote "${remote}" not found`)
40
80
  }
41
81
 
42
- const url = await git(['config', '--get', 'remote.origin.url'])
43
82
  const tmp = await mkdtemp(join(tmpdir(), 'oneclickpush-'))
44
83
 
45
84
  try {
@@ -57,27 +96,27 @@ export async function publish({
57
96
  await git(['remote', 'add', 'origin', url], tmp)
58
97
  }
59
98
 
60
- // Empty the index, then stage everything from dist except excluded paths
99
+ // Empty the index, then stage everything from dir except excluded paths
61
100
  await git(['read-tree', '--empty'], tmp)
62
101
  await git(
63
102
  [
64
103
  '--git-dir',
65
104
  join(tmp, '.git'),
66
105
  '--work-tree',
67
- dist,
106
+ dir,
68
107
  'add',
69
108
  '--force',
70
109
  '--',
71
110
  '.',
72
111
  ...exclude.map((p) => `:(exclude,glob)${p}`)
73
112
  ],
74
- dist
113
+ dir
75
114
  )
76
115
 
77
116
  const status = await git(['diff', '--cached', '--name-status'], tmp)
78
117
  if (!status || dryRun) return status
79
118
 
80
- await git(['commit', '-q', '-m', message], tmp)
119
+ await git(['commit', '-q', '-m', message], tmp, identity)
81
120
  await git(['push', '-q', ...(force ? ['--force'] : []), 'origin', branch], tmp)
82
121
  return status
83
122
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oneclickpush",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "One-click GitHub Pages deployer",
5
5
  "author": "Jason Lee <jason@zerodevx.com>",
6
6
  "scripts": {
@@ -33,6 +33,7 @@
33
33
  "keywords": [
34
34
  "one-click",
35
35
  "gh-pages",
36
- "deploy"
36
+ "deploy",
37
+ "cli"
37
38
  ]
38
39
  }