@angeloashmore/prismic-cli-poc 0.0.0-canary.2ff9563

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 (119) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/dist/index.mjs +1996 -0
  4. package/package.json +52 -0
  5. package/src/custom-type-add-field-boolean.ts +171 -0
  6. package/src/custom-type-add-field-color.ts +158 -0
  7. package/src/custom-type-add-field-date.ts +161 -0
  8. package/src/custom-type-add-field-embed.ts +158 -0
  9. package/src/custom-type-add-field-geo-point.ts +155 -0
  10. package/src/custom-type-add-field-image.ts +158 -0
  11. package/src/custom-type-add-field-key-text.ts +158 -0
  12. package/src/custom-type-add-field-link.ts +180 -0
  13. package/src/custom-type-add-field-number.ts +190 -0
  14. package/src/custom-type-add-field-rich-text.ts +181 -0
  15. package/src/custom-type-add-field-select.ts +164 -0
  16. package/src/custom-type-add-field-timestamp.ts +161 -0
  17. package/src/custom-type-add-field-uid.ts +158 -0
  18. package/src/custom-type-add-field.ts +111 -0
  19. package/src/custom-type-connect-slice.ts +221 -0
  20. package/src/custom-type-create.ts +92 -0
  21. package/src/custom-type-disconnect-slice.ts +179 -0
  22. package/src/custom-type-list.ts +110 -0
  23. package/src/custom-type-remove-field.ts +161 -0
  24. package/src/custom-type-remove.ts +126 -0
  25. package/src/custom-type-set-name.ts +128 -0
  26. package/src/custom-type-view.ts +118 -0
  27. package/src/custom-type.ts +85 -0
  28. package/src/index.ts +100 -0
  29. package/src/init.ts +62 -0
  30. package/src/lib/auth.ts +60 -0
  31. package/src/lib/config.ts +111 -0
  32. package/src/lib/file.ts +49 -0
  33. package/src/lib/json.ts +3 -0
  34. package/src/lib/request.ts +116 -0
  35. package/src/lib/slice.ts +112 -0
  36. package/src/lib/url.ts +25 -0
  37. package/src/locale-add.ts +116 -0
  38. package/src/locale-list.ts +107 -0
  39. package/src/locale-remove.ts +88 -0
  40. package/src/locale-set-default.ts +131 -0
  41. package/src/locale.ts +60 -0
  42. package/src/login.ts +143 -0
  43. package/src/logout.ts +36 -0
  44. package/src/page-type-add-field-boolean.ts +171 -0
  45. package/src/page-type-add-field-color.ts +158 -0
  46. package/src/page-type-add-field-date.ts +161 -0
  47. package/src/page-type-add-field-embed.ts +158 -0
  48. package/src/page-type-add-field-geo-point.ts +155 -0
  49. package/src/page-type-add-field-image.ts +158 -0
  50. package/src/page-type-add-field-key-text.ts +158 -0
  51. package/src/page-type-add-field-link.ts +180 -0
  52. package/src/page-type-add-field-number.ts +190 -0
  53. package/src/page-type-add-field-rich-text.ts +181 -0
  54. package/src/page-type-add-field-select.ts +164 -0
  55. package/src/page-type-add-field-timestamp.ts +161 -0
  56. package/src/page-type-add-field-uid.ts +158 -0
  57. package/src/page-type-add-field.ts +111 -0
  58. package/src/page-type-connect-slice.ts +221 -0
  59. package/src/page-type-create.ts +93 -0
  60. package/src/page-type-disconnect-slice.ts +179 -0
  61. package/src/page-type-list.ts +109 -0
  62. package/src/page-type-remove-field.ts +161 -0
  63. package/src/page-type-remove.ts +126 -0
  64. package/src/page-type-set-name.ts +128 -0
  65. package/src/page-type-set-repeatable.ts +137 -0
  66. package/src/page-type-view.ts +118 -0
  67. package/src/page-type.ts +90 -0
  68. package/src/preview-add.ts +126 -0
  69. package/src/preview-list.ts +106 -0
  70. package/src/preview-remove.ts +109 -0
  71. package/src/preview-set-name.ts +137 -0
  72. package/src/preview.ts +60 -0
  73. package/src/repo-create.ts +136 -0
  74. package/src/repo-list.ts +100 -0
  75. package/src/repo-set-name.ts +102 -0
  76. package/src/repo-view.ts +113 -0
  77. package/src/repo.ts +60 -0
  78. package/src/slice-add-field-boolean.ts +150 -0
  79. package/src/slice-add-field-color.ts +137 -0
  80. package/src/slice-add-field-date.ts +137 -0
  81. package/src/slice-add-field-embed.ts +137 -0
  82. package/src/slice-add-field-geo-point.ts +134 -0
  83. package/src/slice-add-field-image.ts +134 -0
  84. package/src/slice-add-field-key-text.ts +137 -0
  85. package/src/slice-add-field-link.ts +155 -0
  86. package/src/slice-add-field-number.ts +137 -0
  87. package/src/slice-add-field-rich-text.ts +160 -0
  88. package/src/slice-add-field-select.ts +143 -0
  89. package/src/slice-add-field-timestamp.ts +137 -0
  90. package/src/slice-add-field.ts +106 -0
  91. package/src/slice-add-variation.ts +137 -0
  92. package/src/slice-create.ts +129 -0
  93. package/src/slice-list-variations.ts +67 -0
  94. package/src/slice-list.ts +88 -0
  95. package/src/slice-remove-field.ts +117 -0
  96. package/src/slice-remove-variation.ts +108 -0
  97. package/src/slice-remove.ts +81 -0
  98. package/src/slice-rename.ts +112 -0
  99. package/src/slice-view.ts +77 -0
  100. package/src/slice.ts +90 -0
  101. package/src/sync.ts +309 -0
  102. package/src/token-create.ts +185 -0
  103. package/src/token-delete.ts +161 -0
  104. package/src/token-list.ts +212 -0
  105. package/src/token-set-name.ts +165 -0
  106. package/src/token.ts +60 -0
  107. package/src/webhook-add-header.ts +118 -0
  108. package/src/webhook-create.ts +152 -0
  109. package/src/webhook-disable.ts +109 -0
  110. package/src/webhook-enable.ts +132 -0
  111. package/src/webhook-list.ts +93 -0
  112. package/src/webhook-remove-header.ts +117 -0
  113. package/src/webhook-remove.ts +106 -0
  114. package/src/webhook-set-triggers.ts +148 -0
  115. package/src/webhook-status.ts +90 -0
  116. package/src/webhook-test.ts +106 -0
  117. package/src/webhook-view.ts +147 -0
  118. package/src/webhook.ts +95 -0
  119. package/src/whoami.ts +62 -0
@@ -0,0 +1,185 @@
1
+ import { parseArgs } from "node:util";
2
+ import * as v from "valibot";
3
+
4
+ import { isAuthenticated } from "./lib/auth";
5
+ import { safeGetRepositoryFromConfig } from "./lib/config";
6
+ import { stringify } from "./lib/json";
7
+ import { ForbiddenRequestError, request, UnauthorizedRequestError } from "./lib/request";
8
+ import { getRepoUrl } from "./lib/url";
9
+ import { type AccessToken, AccessTokenSchema, getAccessTokens, type OAuthApp, OAuthAppSchema, type WriteToken, WriteTokenSchema } from "./token-list";
10
+
11
+ const HELP = `
12
+ Create a new API token for a Prismic repository.
13
+
14
+ By default, this command reads the repository from prismic.config.json at the
15
+ project root.
16
+
17
+ USAGE
18
+ prismic token create [flags]
19
+
20
+ FLAGS
21
+ -w, --write Create a write token (Custom Types/Migration API)
22
+ -n, --name string Token name (default: "Prismic CLI")
23
+ --allow-releases Allow access to releases (access tokens only)
24
+ --json Output as JSON
25
+ -r, --repo string Repository domain
26
+ -h, --help Show help for command
27
+
28
+ LEARN MORE
29
+ Use \`prismic token <command> --help\` for more information about a command.
30
+ `.trim();
31
+
32
+ const DEFAULT_APP_NAME = "Prismic CLI";
33
+
34
+ export async function tokenCreate(): Promise<void> {
35
+ const {
36
+ values: {
37
+ help,
38
+ repo = await safeGetRepositoryFromConfig(),
39
+ json,
40
+ write,
41
+ name = DEFAULT_APP_NAME,
42
+ "allow-releases": allowReleases,
43
+ },
44
+ } = parseArgs({
45
+ args: process.argv.slice(4), // skip: node, script, "token", "create"
46
+ options: {
47
+ json: { type: "boolean" },
48
+ repo: { type: "string", short: "r" },
49
+ help: { type: "boolean", short: "h" },
50
+ write: { type: "boolean", short: "w" },
51
+ name: { type: "string", short: "n" },
52
+ "allow-releases": { type: "boolean" },
53
+ },
54
+ allowPositionals: false,
55
+ });
56
+
57
+ if (help) {
58
+ console.info(HELP);
59
+ return;
60
+ }
61
+
62
+ if (!repo) {
63
+ console.error("Missing prismic.config.json or --repo option");
64
+ process.exitCode = 1;
65
+ return;
66
+ }
67
+
68
+ if (write && allowReleases) {
69
+ console.error("--allow-releases is only valid for access tokens (not with --write)");
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+
74
+ const authenticated = await isAuthenticated();
75
+ if (!authenticated) {
76
+ handleUnauthenticated();
77
+ return;
78
+ }
79
+
80
+ if (write) {
81
+ const result = await createWriteToken(repo, name);
82
+ if (!result.ok) {
83
+ if (result.error instanceof ForbiddenRequestError || result.error instanceof UnauthorizedRequestError) {
84
+ handleUnauthenticated();
85
+ } else if (v.isValiError(result.error)) {
86
+ console.error(`Failed to create write token: Invalid response: ${stringify(result.error.issues)}`);
87
+ process.exitCode = 1;
88
+ } else {
89
+ console.error(`Failed to create write token: ${stringify(result.value)}`);
90
+ process.exitCode = 1;
91
+ }
92
+ return;
93
+ }
94
+
95
+ if (json) {
96
+ console.info(stringify(result.value));
97
+ } else {
98
+ console.info(`Token created: ${result.value.token}`);
99
+ }
100
+ } else {
101
+ const scope = allowReleases ? "master+releases" : "master";
102
+ const result = await createAccessToken(repo, name, scope);
103
+ if (!result.ok) {
104
+ if (result.error instanceof ForbiddenRequestError || result.error instanceof UnauthorizedRequestError) {
105
+ handleUnauthenticated();
106
+ } else if (v.isValiError(result.error)) {
107
+ console.error(`Failed to create access token: Invalid response: ${stringify(result.error.issues)}`);
108
+ process.exitCode = 1;
109
+ } else {
110
+ console.error(`Failed to create access token: ${stringify(result.value)}`);
111
+ process.exitCode = 1;
112
+ }
113
+ return;
114
+ }
115
+
116
+ if (json) {
117
+ console.info(stringify(result.value));
118
+ } else {
119
+ console.info(`Token created: ${result.value.token}`);
120
+ }
121
+ }
122
+ }
123
+
124
+ type CreateWriteTokenResult =
125
+ | { ok: true; value: WriteToken }
126
+ | { ok: false; value: unknown; error: Error | v.ValiError<typeof WriteTokenSchema> };
127
+
128
+ async function createWriteToken(repo: string, appName: string): Promise<CreateWriteTokenResult> {
129
+ const url = new URL("settings/security/token", await getRepoUrl(repo));
130
+ const response = await request(url, {
131
+ method: "POST",
132
+ body: { app_name: appName },
133
+ schema: WriteTokenSchema,
134
+ });
135
+ return response;
136
+ }
137
+
138
+ type CreateAccessTokenResult =
139
+ | { ok: true; value: AccessToken }
140
+ | { ok: false; value: unknown; error: Error | v.ValiError<typeof AccessTokenSchema> };
141
+
142
+ async function createAccessToken(
143
+ repo: string,
144
+ appName: string,
145
+ scope: "master" | "master+releases",
146
+ ): Promise<CreateAccessTokenResult> {
147
+ // First, find or create an OAuth app with the given name
148
+ const appsResponse = await getAccessTokens(repo);
149
+ if (!appsResponse.ok) {
150
+ return appsResponse;
151
+ }
152
+
153
+ let app = appsResponse.value.find((a: OAuthApp) => a.name === appName);
154
+
155
+ // Create OAuth app if it doesn't exist
156
+ if (!app) {
157
+ const createAppUrl = new URL("settings/security/oauthapp", await getRepoUrl(repo));
158
+ const createAppResponse = await request(createAppUrl, {
159
+ method: "POST",
160
+ body: { app_name: appName },
161
+ schema: OAuthAppSchema,
162
+ });
163
+
164
+ if (!createAppResponse.ok) {
165
+ return createAppResponse;
166
+ }
167
+
168
+ app = createAppResponse.value;
169
+ }
170
+
171
+ // Create the authorization token
172
+ const authUrl = new URL("settings/security/authorizations", await getRepoUrl(repo));
173
+ const authResponse = await request(authUrl, {
174
+ method: "POST",
175
+ body: { app: app.id, scope },
176
+ schema: AccessTokenSchema,
177
+ });
178
+
179
+ return authResponse;
180
+ }
181
+
182
+ function handleUnauthenticated(): void {
183
+ console.error("Not logged in. Run `prismic login` first.");
184
+ process.exitCode = 1;
185
+ }
@@ -0,0 +1,161 @@
1
+ import { parseArgs } from "node:util";
2
+ import * as v from "valibot";
3
+
4
+ import { isAuthenticated } from "./lib/auth";
5
+ import { safeGetRepositoryFromConfig } from "./lib/config";
6
+ import { stringify } from "./lib/json";
7
+ import { ForbiddenRequestError, request, UnauthorizedRequestError } from "./lib/request";
8
+ import { getRepoUrl } from "./lib/url";
9
+ import { type AccessToken, getAccessTokens, getWriteTokens, type WriteToken } from "./token-list";
10
+
11
+ const HELP = `
12
+ Delete a token from a Prismic repository.
13
+
14
+ By default, this command reads the repository from prismic.config.json at the
15
+ project root.
16
+
17
+ USAGE
18
+ prismic token delete <token> [flags]
19
+
20
+ ARGUMENTS
21
+ token The token value (or partial match)
22
+
23
+ FLAGS
24
+ -r, --repo string Repository domain
25
+ -h, --help Show help for command
26
+
27
+ LEARN MORE
28
+ Use \`prismic token <command> --help\` for more information about a command.
29
+ `.trim();
30
+
31
+ export async function tokenDelete(): Promise<void> {
32
+ const {
33
+ values: { help, repo = await safeGetRepositoryFromConfig() },
34
+ positionals: [tokenValue],
35
+ } = parseArgs({
36
+ args: process.argv.slice(4), // skip: node, script, "token", "delete"
37
+ options: {
38
+ repo: { type: "string", short: "r" },
39
+ help: { type: "boolean", short: "h" },
40
+ },
41
+ allowPositionals: true,
42
+ });
43
+
44
+ if (help) {
45
+ console.info(HELP);
46
+ return;
47
+ }
48
+
49
+ if (!tokenValue) {
50
+ console.error("Missing required argument: token");
51
+ process.exitCode = 1;
52
+ return;
53
+ }
54
+
55
+ if (!repo) {
56
+ console.error("Missing prismic.config.json or --repo option");
57
+ process.exitCode = 1;
58
+ return;
59
+ }
60
+
61
+ const authenticated = await isAuthenticated();
62
+ if (!authenticated) {
63
+ handleUnauthenticated();
64
+ return;
65
+ }
66
+
67
+ // First, find the token in access tokens or write tokens
68
+ const [accessResponse, writeResponse] = await Promise.all([
69
+ getAccessTokens(repo),
70
+ getWriteTokens(repo),
71
+ ]);
72
+
73
+ if (!accessResponse.ok) {
74
+ if (accessResponse.error instanceof ForbiddenRequestError || accessResponse.error instanceof UnauthorizedRequestError) {
75
+ handleUnauthenticated();
76
+ } else if (v.isValiError(accessResponse.error)) {
77
+ console.error(`Failed to list access tokens: Invalid response: ${stringify(accessResponse.error.issues)}`);
78
+ process.exitCode = 1;
79
+ } else {
80
+ console.error(`Failed to list access tokens: ${stringify(accessResponse.value)}`);
81
+ process.exitCode = 1;
82
+ }
83
+ return;
84
+ }
85
+
86
+ if (!writeResponse.ok) {
87
+ if (writeResponse.error instanceof ForbiddenRequestError || writeResponse.error instanceof UnauthorizedRequestError) {
88
+ handleUnauthenticated();
89
+ } else if (v.isValiError(writeResponse.error)) {
90
+ console.error(`Failed to list write tokens: Invalid response: ${stringify(writeResponse.error.issues)}`);
91
+ process.exitCode = 1;
92
+ } else {
93
+ console.error(`Failed to list write tokens: ${stringify(writeResponse.value)}`);
94
+ process.exitCode = 1;
95
+ }
96
+ return;
97
+ }
98
+
99
+ // Find in access tokens
100
+ let foundAuth: AccessToken | undefined;
101
+ for (const app of accessResponse.value) {
102
+ for (const auth of app.wroom_auths) {
103
+ if (auth.token === tokenValue || auth.token.startsWith(tokenValue) || auth.token.endsWith(tokenValue)) {
104
+ foundAuth = auth;
105
+ break;
106
+ }
107
+ }
108
+ if (foundAuth) break;
109
+ }
110
+
111
+ if (foundAuth) {
112
+ // Delete the authorization (preserves OAuth app)
113
+ const url = new URL(`settings/security/authorizations/${foundAuth.id}`, await getRepoUrl(repo));
114
+ const response = await request(url, { method: "DELETE" });
115
+
116
+ if (!response.ok) {
117
+ if (response.error instanceof ForbiddenRequestError || response.error instanceof UnauthorizedRequestError) {
118
+ handleUnauthenticated();
119
+ } else {
120
+ console.error(`Failed to delete token: ${stringify(response.value)}`);
121
+ process.exitCode = 1;
122
+ }
123
+ return;
124
+ }
125
+
126
+ console.info("Token deleted");
127
+ return;
128
+ }
129
+
130
+ // Find in write tokens
131
+ const foundWriteToken = writeResponse.value.tokens.find(
132
+ (t: WriteToken) => t.token === tokenValue || t.token.startsWith(tokenValue) || t.token.endsWith(tokenValue),
133
+ );
134
+
135
+ if (foundWriteToken) {
136
+ // Delete write token
137
+ const url = new URL(`settings/security/token/${foundWriteToken.token}`, await getRepoUrl(repo));
138
+ const response = await request(url, { method: "DELETE" });
139
+
140
+ if (!response.ok) {
141
+ if (response.error instanceof ForbiddenRequestError || response.error instanceof UnauthorizedRequestError) {
142
+ handleUnauthenticated();
143
+ } else {
144
+ console.error(`Failed to delete token: ${stringify(response.value)}`);
145
+ process.exitCode = 1;
146
+ }
147
+ return;
148
+ }
149
+
150
+ console.info("Token deleted");
151
+ return;
152
+ }
153
+
154
+ console.error(`Token not found: ${tokenValue}`);
155
+ process.exitCode = 1;
156
+ }
157
+
158
+ function handleUnauthenticated(): void {
159
+ console.error("Not logged in. Run `prismic login` first.");
160
+ process.exitCode = 1;
161
+ }
@@ -0,0 +1,212 @@
1
+ import { parseArgs } from "node:util";
2
+ import * as v from "valibot";
3
+
4
+ import { isAuthenticated } from "./lib/auth";
5
+ import { safeGetRepositoryFromConfig } from "./lib/config";
6
+ import { stringify } from "./lib/json";
7
+ import { ForbiddenRequestError, type ParsedRequestResponse, request, UnauthorizedRequestError } from "./lib/request";
8
+ import { getRepoUrl } from "./lib/url";
9
+
10
+ const HELP = `
11
+ List all API tokens for a Prismic repository.
12
+
13
+ By default, this command reads the repository from prismic.config.json at the
14
+ project root.
15
+
16
+ USAGE
17
+ prismic token list [flags]
18
+
19
+ FLAGS
20
+ --json Output as JSON
21
+ -r, --repo string Repository domain
22
+ -h, --help Show help for command
23
+
24
+ LEARN MORE
25
+ Use \`prismic token <command> --help\` for more information about a command.
26
+ `.trim();
27
+
28
+ export async function tokenList(): Promise<void> {
29
+ const {
30
+ values: { help, repo = await safeGetRepositoryFromConfig(), json },
31
+ } = parseArgs({
32
+ args: process.argv.slice(4), // skip: node, script, "token", "list"
33
+ options: {
34
+ json: { type: "boolean" },
35
+ repo: { type: "string", short: "r" },
36
+ help: { type: "boolean", short: "h" },
37
+ },
38
+ allowPositionals: false,
39
+ });
40
+
41
+ if (help) {
42
+ console.info(HELP);
43
+ return;
44
+ }
45
+
46
+ if (!repo) {
47
+ console.error("Missing prismic.config.json or --repo option");
48
+ process.exitCode = 1;
49
+ return;
50
+ }
51
+
52
+ const authenticated = await isAuthenticated();
53
+ if (!authenticated) {
54
+ handleUnauthenticated();
55
+ return;
56
+ }
57
+
58
+ const [accessResponse, writeResponse] = await Promise.all([
59
+ getAccessTokens(repo),
60
+ getWriteTokens(repo),
61
+ ]);
62
+
63
+ if (!accessResponse.ok) {
64
+ if (accessResponse.error instanceof ForbiddenRequestError || accessResponse.error instanceof UnauthorizedRequestError) {
65
+ handleUnauthenticated();
66
+ } else if (v.isValiError(accessResponse.error)) {
67
+ console.error(
68
+ `Failed to list access tokens: Invalid response: ${stringify(accessResponse.error.issues)}`,
69
+ );
70
+ process.exitCode = 1;
71
+ } else {
72
+ console.error(`Failed to list access tokens: ${stringify(accessResponse.value)}`);
73
+ process.exitCode = 1;
74
+ }
75
+ return;
76
+ }
77
+
78
+ if (!writeResponse.ok) {
79
+ if (writeResponse.error instanceof ForbiddenRequestError || writeResponse.error instanceof UnauthorizedRequestError) {
80
+ handleUnauthenticated();
81
+ } else if (v.isValiError(writeResponse.error)) {
82
+ console.error(
83
+ `Failed to list write tokens: Invalid response: ${stringify(writeResponse.error.issues)}`,
84
+ );
85
+ process.exitCode = 1;
86
+ } else {
87
+ console.error(`Failed to list write tokens: ${stringify(writeResponse.value)}`);
88
+ process.exitCode = 1;
89
+ }
90
+ return;
91
+ }
92
+
93
+ const accessTokens = accessResponse.value.flatMap((app: OAuthApp) =>
94
+ app.wroom_auths.map((auth: AccessToken) => ({
95
+ name: app.name,
96
+ appId: app.id,
97
+ authId: auth.id,
98
+ scope: auth.scope,
99
+ token: auth.token,
100
+ createdAt: auth.created_at.$date,
101
+ })),
102
+ );
103
+ const writeTokens = writeResponse.value.tokens;
104
+
105
+ if (json) {
106
+ console.info(stringify({ accessTokens, writeTokens }));
107
+ } else {
108
+ if (accessTokens.length > 0) {
109
+ console.info("ACCESS TOKENS");
110
+ for (const token of accessTokens) {
111
+ const truncated = truncateToken(token.token);
112
+ const date = formatDate(token.createdAt);
113
+ console.info(` ${token.name} ${token.scope} ${truncated} ${date}`);
114
+ }
115
+ } else {
116
+ console.info("ACCESS TOKENS (none)");
117
+ }
118
+
119
+ console.info("");
120
+
121
+ if (writeTokens.length > 0) {
122
+ console.info("WRITE TOKENS");
123
+ for (const token of writeTokens) {
124
+ const truncated = truncateToken(token.token);
125
+ const date = formatDate(token.timestamp);
126
+ console.info(` ${token.app_name} ${truncated} ${date}`);
127
+ }
128
+ } else {
129
+ console.info("WRITE TOKENS (none)");
130
+ }
131
+ }
132
+ }
133
+
134
+ // MongoDB date format: { "$date": milliseconds }
135
+ const MongoDBDateSchema = v.object({ $date: v.number() });
136
+ type MongoDBDate = v.InferOutput<typeof MongoDBDateSchema>;
137
+
138
+ // Access Token (from OAuth app's wroom_auths array)
139
+ export const AccessTokenSchema = v.object({
140
+ id: v.string(),
141
+ origin: v.string(),
142
+ domain: v.string(),
143
+ app: v.string(),
144
+ scope: v.string(),
145
+ expired_at: MongoDBDateSchema,
146
+ created_at: MongoDBDateSchema,
147
+ owner: v.nullable(v.string()),
148
+ token: v.string(),
149
+ });
150
+ export type AccessToken = v.InferOutput<typeof AccessTokenSchema>;
151
+
152
+ // OAuth App (container for access tokens)
153
+ export const OAuthAppSchema = v.object({
154
+ id: v.string(),
155
+ secret: v.string(),
156
+ name: v.string(),
157
+ owner: v.string(),
158
+ created_at: MongoDBDateSchema,
159
+ authorized_domains: v.array(v.string()),
160
+ wroom_auths: v.array(AccessTokenSchema),
161
+ });
162
+ export type OAuthApp = v.InferOutput<typeof OAuthAppSchema>;
163
+
164
+ // Write Token
165
+ export const WriteTokenSchema = v.object({
166
+ app_name: v.string(),
167
+ token: v.string(),
168
+ timestamp: v.number(),
169
+ });
170
+ export type WriteToken = v.InferOutput<typeof WriteTokenSchema>;
171
+
172
+ // Write Tokens List Response
173
+ const WriteTokensInfoSchema = v.object({
174
+ max_tokens: v.number(),
175
+ tokens: v.array(WriteTokenSchema),
176
+ });
177
+ type WriteTokensInfo = v.InferOutput<typeof WriteTokensInfoSchema>;
178
+
179
+ // Response schemas
180
+ const GetAccessTokensResponseSchema = v.array(OAuthAppSchema);
181
+ type GetAccessTokensResponse = v.InferOutput<typeof GetAccessTokensResponseSchema>;
182
+
183
+ export async function getAccessTokens(
184
+ repo: string,
185
+ ): Promise<ParsedRequestResponse<GetAccessTokensResponse>> {
186
+ const url = new URL("settings/security/contentapi", await getRepoUrl(repo));
187
+ return await request(url, { schema: GetAccessTokensResponseSchema });
188
+ }
189
+
190
+ export async function getWriteTokens(
191
+ repo: string,
192
+ ): Promise<ParsedRequestResponse<WriteTokensInfo>> {
193
+ const url = new URL("settings/security/customtypesapi", await getRepoUrl(repo));
194
+ return await request(url, { schema: WriteTokensInfoSchema });
195
+ }
196
+
197
+ function truncateToken(token: string): string {
198
+ if (token.length <= 12) return token;
199
+ return `${token.slice(0, 8)}...${token.slice(-4)}`;
200
+ }
201
+
202
+ function formatDate(timestamp: number | MongoDBDate): string {
203
+ // MongoDB dates are in milliseconds, plain numbers are in seconds
204
+ const ms = typeof timestamp === "number" ? timestamp * 1000 : timestamp.$date;
205
+ const date = new Date(ms);
206
+ return date.toISOString().split("T")[0];
207
+ }
208
+
209
+ function handleUnauthenticated(): void {
210
+ console.error("Not logged in. Run `prismic login` first.");
211
+ process.exitCode = 1;
212
+ }