@jant/core 0.3.45 → 0.3.47

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 (114) hide show
  1. package/bin/commands/db/execute-file.js +12 -4
  2. package/bin/commands/db/rehearse.js +2 -2
  3. package/bin/commands/export.js +12 -4
  4. package/bin/commands/import-site.js +99 -305
  5. package/bin/commands/migrate.js +36 -69
  6. package/bin/commands/reset-password.js +10 -4
  7. package/bin/commands/site/export.js +59 -248
  8. package/bin/commands/site/snapshot/export.js +58 -45
  9. package/bin/commands/site/snapshot/import.js +104 -52
  10. package/bin/lib/node-env.js +100 -0
  11. package/bin/lib/runtime-target.js +64 -0
  12. package/bin/lib/site-snapshot.js +185 -54
  13. package/bin/lib/sql-export.js +19 -2
  14. package/dist/{app-C-L7wL6o.js → app-3REcR-3U.js} +332 -190
  15. package/dist/app-B67XOEyo.js +6 -0
  16. package/dist/client/.vite/manifest.json +2 -2
  17. package/dist/client/_assets/{client-auth-Dcon89Av.js → client-auth-Ce5WEAVS.js} +236 -183
  18. package/dist/client/_assets/client-s71Js1Cu.css +2 -0
  19. package/dist/{github-sync-CQ1x271f.js → export-ZBlfKSKm.js} +12 -439
  20. package/dist/github-sync-C593r22F.js +4 -0
  21. package/dist/github-sync-bL1hnx3Q.js +428 -0
  22. package/dist/index.js +3 -2
  23. package/dist/node.js +5 -4
  24. package/package.json +3 -2
  25. package/src/__tests__/helpers/export-fixtures.ts +0 -1
  26. package/src/__tests__/import-site-command.test.ts +18 -0
  27. package/src/client/components/__tests__/jant-settings-avatar.test.ts +2 -0
  28. package/src/client/components/__tests__/jant-settings-general.test.ts +70 -0
  29. package/src/client/components/jant-compose-dialog.ts +7 -6
  30. package/src/client/components/jant-compose-editor.ts +6 -5
  31. package/src/client/components/jant-settings-general.ts +164 -22
  32. package/src/client/components/settings-types.ts +4 -6
  33. package/src/client/random-uuid.ts +23 -0
  34. package/src/client-auth.ts +1 -1
  35. package/src/db/__tests__/demo-canonical-snapshot.test.ts +1 -1
  36. package/src/db/__tests__/migration-rehearsal.test.ts +2 -5
  37. package/src/db/backfills/0004_register_apple_touch_media_rows.sql +65 -0
  38. package/src/db/migrations/0021_thankful_phalanx.sql +16 -0
  39. package/src/db/migrations/meta/0021_snapshot.json +2121 -0
  40. package/src/db/migrations/meta/_journal.json +7 -0
  41. package/src/db/migrations/pg/0019_gray_natasha_romanoff.sql +20 -0
  42. package/src/db/migrations/pg/meta/0019_snapshot.json +2718 -0
  43. package/src/db/migrations/pg/meta/_journal.json +7 -0
  44. package/src/db/pg/schema.ts +21 -26
  45. package/src/db/rehearsal-fixtures/demo-current.json +1 -1
  46. package/src/db/schema.ts +16 -20
  47. package/src/i18n/__tests__/middleware.test.ts +43 -1
  48. package/src/i18n/coverage.generated.ts +17 -0
  49. package/src/i18n/i18n.ts +18 -2
  50. package/src/i18n/index.ts +3 -0
  51. package/src/i18n/locales/settings/en.po +16 -11
  52. package/src/i18n/locales/settings/en.ts +1 -1
  53. package/src/i18n/locales/settings/zh-Hans.po +17 -12
  54. package/src/i18n/locales/settings/zh-Hans.ts +1 -1
  55. package/src/i18n/locales/settings/zh-Hant.po +16 -11
  56. package/src/i18n/locales/settings/zh-Hant.ts +1 -1
  57. package/src/i18n/locales.ts +84 -2
  58. package/src/i18n/middleware.ts +25 -16
  59. package/src/i18n/supported-locales.ts +153 -0
  60. package/src/lib/__tests__/csp-builder.test.ts +19 -2
  61. package/src/lib/__tests__/feed.test.ts +242 -1
  62. package/src/lib/__tests__/post-meta.test.ts +0 -1
  63. package/src/lib/__tests__/view.test.ts +0 -1
  64. package/src/lib/csp-builder.ts +28 -10
  65. package/src/lib/feed.ts +153 -3
  66. package/src/middleware/__tests__/secure-headers.test.ts +89 -0
  67. package/src/middleware/auth.ts +1 -1
  68. package/src/middleware/secure-headers.ts +47 -1
  69. package/src/node/__tests__/cli-runtime-target.test.ts +110 -2
  70. package/src/node/__tests__/cli-site-snapshot.test.ts +308 -13
  71. package/src/node/__tests__/cli-site-token-env.test.ts +2 -7
  72. package/src/node/__tests__/cli-snapshot-meta.test.ts +85 -0
  73. package/src/node/__tests__/cli-sql-export.test.ts +49 -0
  74. package/src/node/index.ts +1 -0
  75. package/src/preset.css +8 -2
  76. package/src/routes/api/__tests__/settings.test.ts +3 -2
  77. package/src/routes/api/github-sync.tsx +1 -1
  78. package/src/routes/api/settings.ts +4 -1
  79. package/src/routes/auth/signin.tsx +6 -0
  80. package/src/routes/pages/archive.tsx +4 -2
  81. package/src/services/__tests__/post.test.ts +19 -19
  82. package/src/services/__tests__/search.test.ts +0 -1
  83. package/src/services/__tests__/settings.test.ts +22 -3
  84. package/src/services/bootstrap.ts +7 -3
  85. package/src/services/collection.ts +3 -3
  86. package/src/services/export.ts +0 -3
  87. package/src/services/navigation.ts +0 -2
  88. package/src/services/path.ts +1 -38
  89. package/src/services/post.ts +32 -66
  90. package/src/services/search.ts +0 -6
  91. package/src/services/settings.ts +47 -6
  92. package/src/services/site-admin.ts +6 -1
  93. package/src/styles/ui.css +12 -23
  94. package/src/types/entities.ts +0 -1
  95. package/src/ui/color-themes.ts +1 -1
  96. package/src/ui/dash/settings/GeneralContent.tsx +17 -19
  97. package/src/ui/dash/settings/SettingsRootContent.tsx +17 -28
  98. package/src/ui/feed/NoteCard.tsx +1 -11
  99. package/src/ui/feed/__tests__/timeline-cards.test.ts +1 -1
  100. package/src/ui/pages/HomePage.tsx +1 -4
  101. package/src/ui/pages/PostPage.tsx +2 -0
  102. package/bin/commands/collections.js +0 -268
  103. package/bin/commands/media.js +0 -302
  104. package/bin/commands/posts.js +0 -262
  105. package/bin/commands/search.js +0 -53
  106. package/bin/commands/settings.js +0 -93
  107. package/bin/lib/http-api.js +0 -223
  108. package/bin/lib/media-upload.js +0 -206
  109. package/dist/app-Hvqe7Ks_.js +0 -5
  110. package/dist/client/_assets/client-DDs6NzB3.css +0 -2
  111. package/src/__tests__/bin/content-cli.test.ts +0 -179
  112. package/src/__tests__/bin/media-cli.test.ts +0 -192
  113. /package/dist/{github-api-BkRWnqMx.js → github-api-Bh0PH3zr.js} +0 -0
  114. /package/dist/{github-app-WeadXMb8.js → github-app-D0GvNnqp.js} +0 -0
@@ -1,179 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { run as runCollections } from "../../../bin/commands/collections.js";
3
- import { run as runPosts } from "../../../bin/commands/posts.js";
4
- import { run as runSearch } from "../../../bin/commands/search.js";
5
-
6
- const originalEnv = {
7
- DEV_API_TOKEN: process.env.DEV_API_TOKEN,
8
- JANT_API_TOKEN: process.env.JANT_API_TOKEN,
9
- SITE_ORIGIN: process.env.SITE_ORIGIN,
10
- SITE_PATH_PREFIX: process.env.SITE_PATH_PREFIX,
11
- };
12
-
13
- afterEach(() => {
14
- for (const [key, value] of Object.entries(originalEnv)) {
15
- if (value === undefined) {
16
- delete process.env[key];
17
- } else {
18
- process.env[key] = value;
19
- }
20
- }
21
-
22
- vi.restoreAllMocks();
23
- vi.unstubAllGlobals();
24
- });
25
-
26
- describe("content CLI commands", () => {
27
- it("lists posts through the authenticated API", async () => {
28
- process.env.SITE_ORIGIN = "https://example.com";
29
- process.env.SITE_PATH_PREFIX = "/blog";
30
- process.env.JANT_API_TOKEN = "jant-secret";
31
-
32
- const fetchMock = vi.fn().mockResolvedValue(
33
- new Response(
34
- JSON.stringify({
35
- nextCursor: null,
36
- posts: [{ id: "pst_1", format: "note" }],
37
- }),
38
- {
39
- status: 200,
40
- headers: { "Content-Type": "application/json" },
41
- },
42
- ),
43
- );
44
- vi.stubGlobal("fetch", fetchMock);
45
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
46
-
47
- await runPosts(["list", "--format", "note", "--limit", "5"]);
48
-
49
- expect(fetchMock).toHaveBeenCalledWith(
50
- "https://example.com/blog/api/posts?format=note&limit=5",
51
- {
52
- method: "GET",
53
- headers: {
54
- Accept: "application/json",
55
- Authorization: "Bearer jant-secret",
56
- },
57
- body: undefined,
58
- },
59
- );
60
- expect(JSON.parse(String(logSpy.mock.calls[0]?.[0]))).toEqual({
61
- nextCursor: null,
62
- posts: [{ id: "pst_1", format: "note" }],
63
- });
64
- });
65
-
66
- it("creates a post from inline JSON", async () => {
67
- process.env.SITE_ORIGIN = "https://example.com";
68
- process.env.JANT_API_TOKEN = "jant-secret";
69
-
70
- const fetchMock = vi.fn().mockResolvedValue(
71
- new Response(
72
- JSON.stringify({
73
- id: "pst_created",
74
- format: "note",
75
- bodyText: "Hello from CLI",
76
- }),
77
- {
78
- status: 201,
79
- headers: { "Content-Type": "application/json" },
80
- },
81
- ),
82
- );
83
- vi.stubGlobal("fetch", fetchMock);
84
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
85
-
86
- await runPosts([
87
- "create",
88
- "--json",
89
- JSON.stringify({
90
- format: "note",
91
- bodyMarkdown: "Hello from CLI",
92
- }),
93
- ]);
94
-
95
- expect(fetchMock).toHaveBeenCalledWith("https://example.com/api/posts", {
96
- method: "POST",
97
- headers: {
98
- Accept: "application/json",
99
- Authorization: "Bearer jant-secret",
100
- "Content-Type": "application/json",
101
- },
102
- body: JSON.stringify({
103
- format: "note",
104
- bodyMarkdown: "Hello from CLI",
105
- }),
106
- });
107
- expect(JSON.parse(String(logSpy.mock.calls[0]?.[0]))).toMatchObject({
108
- id: "pst_created",
109
- format: "note",
110
- });
111
- });
112
-
113
- it("falls back to DEV_API_TOKEN for collection mutations", async () => {
114
- process.env.DEV_API_TOKEN = "dev-secret";
115
-
116
- const fetchMock = vi.fn().mockResolvedValue(
117
- new Response(JSON.stringify({ success: true }), {
118
- status: 201,
119
- headers: { "Content-Type": "application/json" },
120
- }),
121
- );
122
- vi.stubGlobal("fetch", fetchMock);
123
- vi.spyOn(console, "log").mockImplementation(() => {});
124
-
125
- await runCollections([
126
- "add-post",
127
- "col_123",
128
- "pst_456",
129
- "--url",
130
- "https://example.com",
131
- ]);
132
-
133
- expect(fetchMock).toHaveBeenCalledWith(
134
- "https://example.com/api/collections/col_123/posts",
135
- {
136
- method: "POST",
137
- headers: {
138
- Accept: "application/json",
139
- Authorization: "Bearer dev-secret",
140
- "Content-Type": "application/json",
141
- },
142
- body: JSON.stringify({ postId: "pst_456" }),
143
- },
144
- );
145
- });
146
-
147
- it("searches without requiring a token", async () => {
148
- process.env.SITE_ORIGIN = "https://example.com";
149
-
150
- const fetchMock = vi.fn().mockResolvedValue(
151
- new Response(
152
- JSON.stringify({
153
- count: 1,
154
- query: "quiet design",
155
- results: [{ id: "pst_1", slug: "quiet-design" }],
156
- }),
157
- {
158
- status: 200,
159
- headers: { "Content-Type": "application/json" },
160
- },
161
- ),
162
- );
163
- vi.stubGlobal("fetch", fetchMock);
164
- vi.spyOn(console, "log").mockImplementation(() => {});
165
-
166
- await runSearch(["quiet", "design"]);
167
-
168
- expect(fetchMock).toHaveBeenCalledWith(
169
- "https://example.com/api/search?q=quiet+design",
170
- {
171
- method: "GET",
172
- headers: {
173
- Accept: "application/json",
174
- },
175
- body: undefined,
176
- },
177
- );
178
- });
179
- });
@@ -1,192 +0,0 @@
1
- import { mkdtemp, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { tmpdir } from "node:os";
4
- import { afterEach, describe, expect, it, vi } from "vitest";
5
- import { run } from "../../../bin/commands/media.js";
6
-
7
- const originalEnv = {
8
- DEV_API_TOKEN: process.env.DEV_API_TOKEN,
9
- JANT_API_TOKEN: process.env.JANT_API_TOKEN,
10
- SITE_ORIGIN: process.env.SITE_ORIGIN,
11
- SITE_PATH_PREFIX: process.env.SITE_PATH_PREFIX,
12
- };
13
-
14
- function createFakeWebpBytes(length = 32) {
15
- const bytes = new Uint8Array(length);
16
- bytes.set([
17
- 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
18
- ]);
19
- return bytes;
20
- }
21
-
22
- afterEach(() => {
23
- for (const [key, value] of Object.entries(originalEnv)) {
24
- if (value === undefined) {
25
- delete process.env[key];
26
- } else {
27
- process.env[key] = value;
28
- }
29
- }
30
-
31
- vi.restoreAllMocks();
32
- vi.unstubAllGlobals();
33
- });
34
-
35
- describe("media CLI commands", () => {
36
- it("lists media through the authenticated API", async () => {
37
- process.env.SITE_ORIGIN = "https://example.com";
38
- process.env.JANT_API_TOKEN = "jant-secret";
39
-
40
- const fetchMock = vi.fn().mockResolvedValue(
41
- new Response(
42
- JSON.stringify({
43
- media: [{ id: "med_1", mimeType: "image/webp" }],
44
- }),
45
- {
46
- status: 200,
47
- headers: { "Content-Type": "application/json" },
48
- },
49
- ),
50
- );
51
- vi.stubGlobal("fetch", fetchMock);
52
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
53
-
54
- await run(["list", "--mimePrefix", "image/"]);
55
-
56
- expect(fetchMock).toHaveBeenCalledWith(
57
- "https://example.com/api/upload?mimePrefix=image%2F",
58
- {
59
- method: "GET",
60
- headers: {
61
- Accept: "application/json",
62
- Authorization: "Bearer jant-secret",
63
- },
64
- body: undefined,
65
- },
66
- );
67
- expect(JSON.parse(String(logSpy.mock.calls[0]?.[0]))).toEqual({
68
- media: [{ id: "med_1", mimeType: "image/webp" }],
69
- });
70
- });
71
-
72
- it("uploads media through the upload session API and patches alt text", async () => {
73
- process.env.SITE_ORIGIN = "https://example.com";
74
- process.env.JANT_API_TOKEN = "jant-secret";
75
-
76
- const tempDir = await mkdtemp(join(tmpdir(), "jant-media-cli-"));
77
- const filePath = join(tempDir, "photo.webp");
78
- await writeFile(filePath, createFakeWebpBytes());
79
-
80
- const fetchMock = vi
81
- .fn()
82
- .mockResolvedValueOnce(
83
- new Response(
84
- JSON.stringify({
85
- id: "ups_1",
86
- transport: {
87
- kind: "relay",
88
- url: "/api/uploads/ups_1/body",
89
- },
90
- }),
91
- {
92
- status: 200,
93
- headers: { "Content-Type": "application/json" },
94
- },
95
- ),
96
- )
97
- .mockResolvedValueOnce(new Response(null, { status: 204 }))
98
- .mockResolvedValueOnce(
99
- new Response(
100
- JSON.stringify({
101
- id: "med_1",
102
- filename: "med_1-photo.webp",
103
- mimeType: "image/webp",
104
- size: 32,
105
- url: "/media/photo.webp",
106
- }),
107
- {
108
- status: 200,
109
- headers: { "Content-Type": "application/json" },
110
- },
111
- ),
112
- )
113
- .mockResolvedValueOnce(
114
- new Response(
115
- JSON.stringify({
116
- id: "med_1",
117
- alt: "Cover image",
118
- mimeType: "image/webp",
119
- type: "media",
120
- url: "/media/photo.webp",
121
- }),
122
- {
123
- status: 200,
124
- headers: { "Content-Type": "application/json" },
125
- },
126
- ),
127
- );
128
- vi.stubGlobal("fetch", fetchMock);
129
- const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
130
-
131
- await run(["upload", filePath, "--alt", "Cover image"]);
132
-
133
- expect(fetchMock).toHaveBeenNthCalledWith(
134
- 1,
135
- "https://example.com/api/uploads/init",
136
- {
137
- method: "POST",
138
- headers: {
139
- Accept: "application/json",
140
- Authorization: "Bearer jant-secret",
141
- "Content-Type": "application/json",
142
- },
143
- body: JSON.stringify({
144
- filename: "photo.webp",
145
- contentType: "image/webp",
146
- size: 32,
147
- }),
148
- },
149
- );
150
- expect(fetchMock).toHaveBeenNthCalledWith(
151
- 2,
152
- "https://example.com/api/uploads/ups_1/body",
153
- {
154
- method: "PUT",
155
- headers: {
156
- Authorization: "Bearer jant-secret",
157
- },
158
- body: Buffer.from(createFakeWebpBytes()),
159
- },
160
- );
161
- expect(fetchMock).toHaveBeenNthCalledWith(
162
- 3,
163
- "https://example.com/api/uploads/ups_1/complete",
164
- {
165
- method: "POST",
166
- headers: {
167
- Accept: "application/json",
168
- Authorization: "Bearer jant-secret",
169
- "Content-Type": "application/json",
170
- },
171
- body: JSON.stringify({}),
172
- },
173
- );
174
- expect(fetchMock).toHaveBeenNthCalledWith(
175
- 4,
176
- "https://example.com/api/upload/med_1",
177
- {
178
- method: "PATCH",
179
- headers: {
180
- Accept: "application/json",
181
- Authorization: "Bearer jant-secret",
182
- "Content-Type": "application/json",
183
- },
184
- body: JSON.stringify({ alt: "Cover image" }),
185
- },
186
- );
187
- expect(JSON.parse(String(logSpy.mock.calls[0]?.[0]))).toMatchObject({
188
- id: "med_1",
189
- alt: "Cover image",
190
- });
191
- });
192
- });