notion_publish 0.1.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +36 -0
  3. data/LICENSE +21 -0
  4. data/README.md +221 -0
  5. data/docs/notion-publish-home-page.md +105 -0
  6. data/docs/usage.md +648 -0
  7. data/exe/notion-publish +6 -0
  8. data/lib/notion_publish/adopter.rb +77 -0
  9. data/lib/notion_publish/cli.rb +212 -0
  10. data/lib/notion_publish/client.rb +226 -0
  11. data/lib/notion_publish/commands/adopt.rb +114 -0
  12. data/lib/notion_publish/commands/command.rb +114 -0
  13. data/lib/notion_publish/commands/properties.rb +33 -0
  14. data/lib/notion_publish/commands/publish.rb +134 -0
  15. data/lib/notion_publish/commands/relink.rb +95 -0
  16. data/lib/notion_publish/commands/reporting.rb +57 -0
  17. data/lib/notion_publish/commands/republish.rb +156 -0
  18. data/lib/notion_publish/commands/status.rb +84 -0
  19. data/lib/notion_publish/commands/whoami.rb +27 -0
  20. data/lib/notion_publish/commands.rb +17 -0
  21. data/lib/notion_publish/decoration.rb +75 -0
  22. data/lib/notion_publish/document.rb +75 -0
  23. data/lib/notion_publish/errors.rb +62 -0
  24. data/lib/notion_publish/fixups.rb +139 -0
  25. data/lib/notion_publish/links.rb +65 -0
  26. data/lib/notion_publish/log.rb +49 -0
  27. data/lib/notion_publish/manifest.rb +224 -0
  28. data/lib/notion_publish/media.rb +90 -0
  29. data/lib/notion_publish/notion_digest.rb +23 -0
  30. data/lib/notion_publish/pool.rb +103 -0
  31. data/lib/notion_publish/progress.rb +103 -0
  32. data/lib/notion_publish/property_set.rb +116 -0
  33. data/lib/notion_publish/publisher.rb +475 -0
  34. data/lib/notion_publish/reference.rb +84 -0
  35. data/lib/notion_publish/resolver.rb +216 -0
  36. data/lib/notion_publish/schema.rb +206 -0
  37. data/lib/notion_publish/settings.rb +73 -0
  38. data/lib/notion_publish/sharing.rb +47 -0
  39. data/lib/notion_publish/status.rb +127 -0
  40. data/lib/notion_publish/target.rb +34 -0
  41. data/lib/notion_publish/uploader.rb +85 -0
  42. data/lib/notion_publish/users.rb +49 -0
  43. data/lib/notion_publish/version.rb +5 -0
  44. data/lib/notion_publish.rb +31 -0
  45. metadata +96 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5a9b4306daec96d259c074e2386d006468f5adc2500764b8559b1b65c169e1ba
4
+ data.tar.gz: 7015f9427836474584cb1665f81d509bda08e74329b653eda7b5692ce1daa740
5
+ SHA512:
6
+ metadata.gz: c78e9ab6ef5ad4c8e428208bf878c0c89fffd8fdbfdd896e326a7e640e31713c8beab0ad2683677a04b03e0f5db87052bd09dd66ddb525fc65a77e800abe009e
7
+ data.tar.gz: ee0afe9af082ac9dbc238146b2f8d12037d9b3f1ce6d247e1552a1ecc130d8b18e713d2374f1ca4050b3edc41bf5b0bf5a97cf24523a9e7b115ca61998f1c7f3
data/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2026-09-26
8
+
9
+ First public release.
10
+
11
+ ### Added
12
+
13
+ - Publish a Markdown file to a Notion page, database, or data source, named by ID, URL, or exact
14
+ database name. Notion does the Markdown conversion.
15
+ - Update the same page in place on later runs. `notion-publish-manifest.yml`, which you commit,
16
+ records which page each file became, so a tracked file needs no destination.
17
+ - `republish [DIR]` updates every page the manifest tracks, from each file's front matter plus the
18
+ `--title` and `--keep-h1` it was last published with. Properties set with flags are left alone.
19
+ - Set database properties from front matter, `--properties-json`, or `--property`, validated
20
+ against the live schema before anything is written. Properties the tool set before and no longer
21
+ sets are cleared; properties it never set are left alone.
22
+ - Detect edits made in Notion since the last publish, and pages moved to Notion's trash. An edited
23
+ page stops the publish with exit code 3 unless `--force` is given; a trashed page is published
24
+ afresh.
25
+ - Upload local images, including images under list items, and place them as image blocks.
26
+ - Join hard-wrapped lines before sending, since Notion makes a block of each line.
27
+ - Rewrite links between published documents to their Notion URLs, with `relink` for a second pass.
28
+ - Page icon and cover from an emoji, URL, or local file.
29
+ - `status`, `relink`, `adopt`, and `properties` subcommands.
30
+ - Check three pages at a time in `republish`, `status`, and `relink`; `-j`/`--jobs` changes the
31
+ number. A progress line appears when stderr is a terminal.
32
+ - Output lists only files where something happened. `-v` lists every file, `-vv` logs each API
33
+ request, and `-vvv` adds shortened bodies. The token is never logged.
34
+ - `--dry-run` and `--json` output for scripts and CI.
35
+
36
+ [0.1.0]: https://github.com/outsidecto/notion-publish/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Outside CTO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # notion-publish
2
+
3
+ [![CI](https://github.com/outsidecto/notion-publish/actions/workflows/ci.yml/badge.svg)](https://github.com/outsidecto/notion-publish/actions/workflows/ci.yml)
4
+
5
+ Keep Notion pages in step with Markdown files in a Git repository.
6
+
7
+ Many teams write their documents in Git, where changes are reviewed and every version is kept. The
8
+ people who read those documents often work in Notion. `notion-publish` publishes each Markdown file
9
+ as a Notion page, and later runs update the same page in place. Git stays the source of truth, and
10
+ Notion shows a current copy.
11
+
12
+ ```console
13
+ $ notion-publish policies/access-control.md --parent Policies --link
14
+ Published policies/access-control.md to database "Policies" (2efab123-...)
15
+ https://app.notion.com/p/Access-Control-Policy-3cfab123cd45818b9a72c2bd16e85a62
16
+
17
+ $ notion-publish policies/access-control.md
18
+ Unchanged policies/access-control.md
19
+ ```
20
+
21
+ ## What it does
22
+
23
+ - **Updates pages in place.** A committed file, `notion-publish-manifest.yml`, records which page
24
+ each file became. Republishing replaces the page body and keeps its URL, so links to it keep
25
+ working.
26
+ - **Republishes everything it tracks** with one command, `notion-publish republish`, using what it
27
+ recorded about each file.
28
+ - **Protects edits made in Notion.** If someone edited the page in Notion since the last publish,
29
+ the tool stops and exits with code 3 rather than overwriting their work.
30
+ - **Sets database properties** from front matter or flags. Values are checked against the database
31
+ schema before anything is written, so a typo in a select option fails instead of creating a new
32
+ option.
33
+ - **Rewrites links between documents** to their Notion URLs.
34
+ - **Uploads local images** and places them in the page.
35
+ - **Fixes wrapped text.** Notion treats each line of a hard-wrapped paragraph as its own block. The
36
+ tool joins the lines first.
37
+ - **Reports drift.** `notion-publish status` lists files changed locally, pages changed in Notion
38
+ or moved to Notion's trash, and files that were never published.
39
+
40
+ Notion does the Markdown conversion itself, through its
41
+ [Markdown API](https://developers.notion.com/). The gem has no runtime dependencies beyond the Ruby
42
+ standard library.
43
+
44
+ ## Install
45
+
46
+ Requires Ruby 3.2 or later.
47
+
48
+ gem install notion_publish
49
+
50
+ Or add it to a Gemfile:
51
+
52
+ ```ruby
53
+ gem "notion_publish", require: false
54
+ ```
55
+
56
+ ## Quick start
57
+
58
+ 1. **Create a connection.** Go to
59
+ [notion.so/profile/integrations](https://www.notion.so/profile/integrations), create an internal
60
+ connection, and give it the Read, Update, and Insert content capabilities. Copy the token.
61
+
62
+ 2. **Share a destination with it.** In Notion, open the page or database you will publish into,
63
+ choose **••• → Connections**, and add your connection.
64
+
65
+ 3. **Set the token.**
66
+
67
+ export NOTION_API_TOKEN=ntn_...
68
+ notion-publish --whoami
69
+
70
+ 4. **Publish.** `--parent` takes a page or database ID, a Notion URL, or the exact name of a
71
+ database.
72
+
73
+ notion-publish docs/onboarding.md --link \
74
+ --parent 'https://app.notion.com/p/Team-Docs-2efab123...'
75
+
76
+ `--link` creates `notion-publish-manifest.yml` at the repository root. Commit it. Without it,
77
+ the next run cannot find the page and will create a second copy.
78
+
79
+ 5. **Edit the file and publish again.** The page is updated in place.
80
+
81
+ ## Keeping a repository in sync
82
+
83
+ Put the destination in a settings file, so it does not have to be typed each time. Settings apply
84
+ to the directory they are in and everything below it.
85
+
86
+ ```yaml
87
+ # policies/.notion-publish.yml
88
+ database: Policies
89
+ icon: 📘
90
+ ```
91
+
92
+ Set database properties in each file's front matter:
93
+
94
+ ```markdown
95
+ ---
96
+ properties:
97
+ Owner: Jane Doe
98
+ Status: Approved
99
+ Review Date: 2027-01-15
100
+ ---
101
+ # Access Control Policy
102
+ ...
103
+ ```
104
+
105
+ The first time, publish the directory with a loop. Then run `relink` to fix links to documents
106
+ that were published later in the loop:
107
+
108
+ ```sh
109
+ for f in policies/*.md; do
110
+ notion-publish "$f" --link
111
+ done
112
+ notion-publish relink
113
+ ```
114
+
115
+ After that, one command updates every page the manifest tracks:
116
+
117
+ ```console
118
+ $ notion-publish republish
119
+ Updated policies/incident-response.md to database "Policies" (2efab123-...)
120
+ https://app.notion.com/p/Incident-Response-Policy-...
121
+ 12 documents: 11 unchanged, 1 updated
122
+ ```
123
+
124
+ Only files where something happened are listed. `-v` lists every file. In a terminal, a progress
125
+ line shows which file is being checked while it runs.
126
+
127
+ `republish` needs no file names and no destination. Each page is updated where it already is, from
128
+ the file's current front matter plus any `--title` or `--keep-h1` it was last published with.
129
+ Properties that were set with `--property` are left as they are. A new file is not picked up until
130
+ you publish it once by name.
131
+
132
+ Check where things stand at any time:
133
+
134
+ ```console
135
+ $ notion-publish status
136
+ /repo/notion-publish-manifest.yml -- 12 tracked
137
+
138
+ Changed locally (1)
139
+ policies/access-control.md
140
+
141
+ Changed in Notion (1)
142
+ policies/incident-response.md
143
+ https://app.notion.com/p/...
144
+
145
+ 10 in sync, 1 changed locally, 1 changed in Notion
146
+ ```
147
+
148
+ `status -v` also lists the documents that are in sync.
149
+
150
+ ### Publishing from GitHub Actions
151
+
152
+ This workflow republishes on every push to `main` and commits the updated manifest back.
153
+ Store the token as a repository secret named `NOTION_API_TOKEN`.
154
+
155
+ ```yaml
156
+ name: Publish to Notion
157
+
158
+ on:
159
+ push:
160
+ branches: [main]
161
+ paths: ["policies/**.md"]
162
+
163
+ permissions:
164
+ contents: write
165
+
166
+ jobs:
167
+ publish:
168
+ runs-on: ubuntu-latest
169
+ steps:
170
+ - uses: actions/checkout@v5
171
+ - uses: ruby/setup-ruby@v1
172
+ with:
173
+ ruby-version: "3.4"
174
+ - run: gem install notion_publish
175
+ - name: Republish
176
+ env:
177
+ NOTION_API_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
178
+ run: notion-publish republish
179
+ - name: Commit notion-publish-manifest.yml
180
+ if: success() || failure()
181
+ run: |
182
+ git config user.name "github-actions[bot]"
183
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
184
+ git add notion-publish-manifest.yml
185
+ if ! git diff --cached --quiet; then
186
+ git commit -m "Record published Notion pages"
187
+ git push
188
+ fi
189
+ ```
190
+
191
+ If a page was edited in Notion, `republish` leaves it alone and exits with code 3, which fails the
192
+ job. The other files still publish. Look at the page, move the change into the Markdown, and publish
193
+ that file with `--force`.
194
+
195
+ ## Documentation
196
+
197
+ [docs/usage.md](docs/usage.md) is the full reference. It covers destinations, settings, every
198
+ property type, the manifest, adopting pages that already exist, images, exit codes, and JSON
199
+ output.
200
+
201
+ ## Known limits
202
+
203
+ - A new file must be published once by name before `republish` includes it.
204
+ - Images inside a paragraph cannot be placed, since Notion has no inline images.
205
+ - Files over 20 MB are not uploaded, because Notion's multi-part upload is not implemented.
206
+ - H5 and H6 become heading 4 in Notion.
207
+
208
+ ## Development
209
+
210
+ bundle install
211
+ bundle exec rake # tests and RuboCop
212
+
213
+ Tests use Minitest and WebMock and make no network calls. Bug reports and pull requests are welcome
214
+ at [github.com/outsidecto/notion-publish](https://github.com/outsidecto/notion-publish). To report a
215
+ security problem, see [SECURITY.md](SECURITY.md).
216
+
217
+ ## License
218
+
219
+ MIT. See [LICENSE](LICENSE).
220
+
221
+ This project is not affiliated with or endorsed by Notion Labs, Inc.
@@ -0,0 +1,105 @@
1
+ # notion_publish
2
+
3
+ Many teams keep their important documents in Git, where changes get reviewed and every version is
4
+ kept. The people who read those documents often work in Notion instead. So somebody copies each
5
+ document over by hand, the two copies drift apart, and after a few months nobody can say which one
6
+ is current.
7
+
8
+ `notion_publish` is a command-line tool that publishes Markdown files from a Git repository into
9
+ Notion and then keeps those pages up to date. Git stays the authoritative copy. Notion shows a
10
+ current version of it.
11
+
12
+ It was built for a security and compliance corpus, where an auditor can reasonably ask which Notion
13
+ page corresponds to a given policy, and where "somebody pasted it in last spring" is not a good
14
+ answer.
15
+
16
+ ## What it looks like
17
+
18
+ Publish a file. `--parent` takes a page ID, a Notion URL, or the exact name of a database:
19
+
20
+ $ notion-publish policies/access-control.md --parent 'Policies' --link
21
+ Published policies/access-control.md to database "Policies" (2efab123-...)
22
+ https://app.notion.com/p/Access-Control-Policy-3cfab123cd45818b9a72c2bd16e85a62
23
+
24
+ `--link` writes a file called `notion-publish-manifest.yml`, which records the page each document
25
+ became. You commit that file. From then on, one command brings every page up to date:
26
+
27
+ $ notion-publish republish
28
+ Updated policies/incident-response.md to database "Policies" (2efab123-...)
29
+ https://app.notion.com/p/Incident-Response-Policy-...
30
+ 12 documents: 11 unchanged, 1 updated
31
+
32
+ Before publishing anything, you can ask what has moved apart:
33
+
34
+ $ notion-publish status
35
+ /repo/notion-publish-manifest.yml -- 12 tracked
36
+
37
+ Changed locally (1)
38
+ policies/access-control.md
39
+
40
+ Changed in Notion (1)
41
+ policies/incident-response.md
42
+ https://app.notion.com/p/...
43
+
44
+ 10 in sync, 1 changed locally, 1 changed in Notion
45
+
46
+ `status` exits with code 3 when something needs a person to look at it, so it also works as a check
47
+ in continuous integration.
48
+
49
+ ## The parts that are easy to get wrong
50
+
51
+ - **Updating instead of duplicating.** Because the manifest records which page each file became,
52
+ publishing again replaces that page's contents and keeps its URL. Links people have already
53
+ shared keep working.
54
+ - **Edits made in Notion.** The tool records what Notion returned after each publish and reads the
55
+ page again before the next one. If somebody edited the page, it stops and says so rather than
56
+ overwriting their work.
57
+ - **Pages deleted in Notion.** Deleting a page in Notion moves it to the trash, where the API still
58
+ returns it with all its content. The tool notices the difference and publishes a fresh page.
59
+ - **Database properties.** Values are checked against the live database schema before anything is
60
+ written, so a misspelled select option is an error instead of a brand-new option added to your
61
+ schema. Properties the tool set before and no longer sets are cleared; properties it never set are
62
+ left alone.
63
+ - **Links between documents.** A relative link to another Markdown file does not merely break in
64
+ Notion. Notion turns it into a link to a domain nobody owns. Links to published documents are
65
+ rewritten to their Notion URLs.
66
+ - **Local images.** Notion's Markdown import only understands images at public URLs, and a local
67
+ path produces an empty image block with no error at all. Local images are uploaded and placed as
68
+ real image blocks.
69
+ - **Hard-wrapped text.** Notion makes a separate block out of each line, so a file wrapped at a
70
+ column width arrives looking double-spaced. Wrapped paragraphs and list items are joined back
71
+ together first.
72
+
73
+ ## Why another gem
74
+
75
+ Notion does not publish an official Ruby SDK. The client it maintains is for JavaScript.
76
+
77
+ Ruby has a dozen unofficial Notion gems, and they address two different problems. Most are API
78
+ clients that wrap the HTTP endpoints, and the widely used ones have not seen a release in some time:
79
+ `notion-ruby-client` last shipped in October 2023, `notion-sdk-ruby` in June 2022, and `notion` in
80
+ January 2021. Notion reorganized its data model in September 2025, when a database became a
81
+ container holding one or more data sources, and the schema and rows moved to the data source. A
82
+ client written before that models an API that no longer works the same way.
83
+
84
+ The gems that do handle Markdown run in the other direction. `notion_to_md` exports pages from
85
+ Notion into Markdown, and `notion_rails` renders Notion blocks as HTML inside a Rails application.
86
+ Both are useful. Neither publishes into Notion, and neither keeps a record of what was published,
87
+ which is the part that makes a second run safe instead of duplicating everything.
88
+
89
+ The conversion works differently here as well. Tools in this area usually build Notion's block JSON
90
+ themselves, which means carrying a Markdown parser and keeping it in step with Notion's block types.
91
+ `notion_publish` sends the Markdown and lets Notion parse it, using the Markdown parameter Notion's
92
+ own API accepts. Tables, nested lists, task lists, and fenced code arrive correctly because Notion
93
+ did that work. It is also why the gem has no runtime dependencies at all: the client is `Net::HTTP`
94
+ from the Ruby standard library.
95
+
96
+ ## Getting it
97
+
98
+ Requires Ruby 3.2 or later.
99
+
100
+ gem install notion_publish
101
+
102
+ The source code, the full usage reference, and the issue tracker are on GitHub:
103
+ [github.com/outsidecto/notion-publish](https://github.com/outsidecto/notion-publish).
104
+
105
+ MIT licensed. This project is not affiliated with or endorsed by Notion Labs, Inc.