alchemy 0.22.3 → 0.23.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/lib/aws/bucket.d.ts.map +1 -1
- package/lib/aws/bucket.js +13 -12
- package/lib/aws/bucket.js.map +1 -1
- package/lib/aws/control/client.d.ts.map +1 -1
- package/lib/aws/control/client.js +5 -3
- package/lib/aws/control/client.js.map +1 -1
- package/lib/aws/control/resource.d.ts.map +1 -1
- package/lib/aws/control/resource.js +13 -4
- package/lib/aws/control/resource.js.map +1 -1
- package/lib/aws/control/types.d.ts +85129 -0
- package/lib/aws/function.d.ts.map +1 -1
- package/lib/aws/function.js +51 -42
- package/lib/aws/function.js.map +1 -1
- package/lib/aws/index.d.ts +1 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/policy-attachment.d.ts.map +1 -1
- package/lib/aws/policy-attachment.js +5 -4
- package/lib/aws/policy-attachment.js.map +1 -1
- package/lib/aws/policy.d.ts.map +1 -1
- package/lib/aws/policy.js +21 -20
- package/lib/aws/policy.js.map +1 -1
- package/lib/aws/queue.d.ts.map +1 -1
- package/lib/aws/queue.js +19 -15
- package/lib/aws/queue.js.map +1 -1
- package/lib/aws/retry.d.ts +5 -0
- package/lib/aws/retry.d.ts.map +1 -0
- package/lib/aws/retry.js +27 -0
- package/lib/aws/retry.js.map +1 -0
- package/lib/aws/role.d.ts.map +1 -1
- package/lib/aws/role.js +31 -30
- package/lib/aws/role.js.map +1 -1
- package/lib/aws/ses.d.ts.map +1 -1
- package/lib/aws/ses.js +27 -26
- package/lib/aws/ses.js.map +1 -1
- package/lib/aws/ssm-parameter.d.ts +150 -0
- package/lib/aws/ssm-parameter.d.ts.map +1 -0
- package/lib/aws/ssm-parameter.js +184 -0
- package/lib/aws/ssm-parameter.js.map +1 -0
- package/lib/aws/table.d.ts.map +1 -1
- package/lib/aws/table.js +9 -27
- package/lib/aws/table.js.map +1 -1
- package/lib/cloudflare/account-id.d.ts +1 -0
- package/lib/cloudflare/account-id.d.ts.map +1 -1
- package/lib/cloudflare/account-id.js.map +1 -1
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +22 -2
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +2 -1
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +2 -1
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/index.d.ts +2 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +2 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/queue.d.ts.map +1 -1
- package/lib/cloudflare/queue.js +1 -0
- package/lib/cloudflare/queue.js.map +1 -1
- package/lib/cloudflare/route.d.ts +7 -0
- package/lib/cloudflare/route.d.ts.map +1 -1
- package/lib/cloudflare/route.js +44 -2
- package/lib/cloudflare/route.js.map +1 -1
- package/lib/cloudflare/version-metadata.d.ts +7 -0
- package/lib/cloudflare/version-metadata.d.ts.map +1 -0
- package/lib/cloudflare/version-metadata.js +7 -0
- package/lib/cloudflare/version-metadata.js.map +1 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +7 -2
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +2 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +17 -8
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/workflow.d.ts +7 -0
- package/lib/cloudflare/workflow.d.ts.map +1 -1
- package/lib/cloudflare/workflow.js +2 -0
- package/lib/cloudflare/workflow.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +7 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +14 -2
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/destroy.js +1 -1
- package/lib/destroy.js.map +1 -1
- package/lib/dns/godaddy.d.ts.map +1 -1
- package/lib/dns/godaddy.js +2 -1
- package/lib/dns/godaddy.js.map +1 -1
- package/lib/fs/file-system-state-store.d.ts.map +1 -1
- package/lib/fs/file-system-state-store.js +14 -2
- package/lib/fs/file-system-state-store.js.map +1 -1
- package/lib/fs/static-json-file.d.ts +1 -0
- package/lib/fs/static-json-file.d.ts.map +1 -1
- package/lib/fs/static-json-file.js +11 -8
- package/lib/fs/static-json-file.js.map +1 -1
- package/lib/github/secret.d.ts.map +1 -1
- package/lib/github/secret.js +1 -0
- package/lib/github/secret.js.map +1 -1
- package/lib/neon/api.d.ts.map +1 -1
- package/lib/neon/api.js +2 -1
- package/lib/neon/api.js.map +1 -1
- package/lib/runtime/shims.d.ts +6 -0
- package/lib/runtime/shims.d.ts.map +1 -0
- package/lib/runtime/shims.js +27 -0
- package/lib/runtime/shims.js.map +1 -0
- package/lib/sentry/api.d.ts.map +1 -1
- package/lib/sentry/api.js +2 -1
- package/lib/sentry/api.js.map +1 -1
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +1 -7
- package/lib/test/bun.js.map +1 -1
- package/lib/test/prune.d.ts +2 -1
- package/lib/test/prune.d.ts.map +1 -1
- package/lib/test/prune.js +29 -5
- package/lib/test/prune.js.map +1 -1
- package/lib/test/vitest.d.ts +92 -0
- package/lib/test/vitest.d.ts.map +1 -0
- package/lib/test/vitest.js +94 -0
- package/lib/test/vitest.js.map +1 -0
- package/lib/upstash/api.d.ts.map +1 -1
- package/lib/upstash/api.js +2 -1
- package/lib/upstash/api.js.map +1 -1
- package/lib/util/retry.js +2 -2
- package/lib/util/retry.js.map +1 -1
- package/lib/util/safe-fetch.d.ts +2 -0
- package/lib/util/safe-fetch.d.ts.map +1 -0
- package/lib/util/safe-fetch.js +48 -0
- package/lib/util/safe-fetch.js.map +1 -0
- package/lib/vercel/api.d.ts.map +1 -1
- package/lib/vercel/api.js +2 -1
- package/lib/vercel/api.js.map +1 -1
- package/lib/web/vitepress/process-front-matter-files.d.ts.map +1 -1
- package/lib/web/vitepress/process-front-matter-files.js +11 -7
- package/lib/web/vitepress/process-front-matter-files.js.map +1 -1
- package/package.json +12 -1
- package/src/alchemy.ts +2 -2
- package/src/aws/bucket.ts +55 -36
- package/src/aws/control/client.ts +13 -13
- package/src/aws/control/resource.ts +19 -3
- package/src/aws/function.ts +233 -178
- package/src/aws/index.ts +1 -0
- package/src/aws/policy-attachment.ts +15 -10
- package/src/aws/policy.ts +75 -54
- package/src/aws/queue.ts +53 -36
- package/src/aws/retry.ts +38 -0
- package/src/aws/role.ts +121 -90
- package/src/aws/ses.ts +101 -73
- package/src/aws/ssm-parameter.ts +315 -0
- package/src/aws/table.ts +61 -90
- package/src/cloudflare/account-id.ts +2 -0
- package/src/cloudflare/api.ts +26 -3
- package/src/cloudflare/bindings.ts +2 -0
- package/src/cloudflare/bound.ts +8 -5
- package/src/cloudflare/index.ts +2 -0
- package/src/cloudflare/queue.ts +2 -0
- package/src/cloudflare/route.ts +86 -2
- package/src/cloudflare/version-metadata.ts +6 -0
- package/src/cloudflare/worker-metadata.ts +6 -2
- package/src/cloudflare/worker.ts +21 -9
- package/src/cloudflare/workflow.ts +8 -0
- package/src/cloudflare/wrangler.json.ts +29 -3
- package/src/destroy.ts +1 -1
- package/src/dns/godaddy.ts +3 -1
- package/src/fs/file-system-state-store.ts +16 -2
- package/src/fs/static-json-file.ts +12 -8
- package/src/github/secret.ts +2 -0
- package/src/neon/api.ts +2 -1
- package/src/sentry/api.ts +2 -1
- package/src/test/bun.ts +1 -7
- package/src/test/prune.ts +31 -4
- package/src/test/vitest.ts +209 -0
- package/src/upstash/api.ts +2 -1
- package/src/util/retry.ts +3 -3
- package/src/util/safe-fetch.ts +61 -0
- package/src/vercel/api.ts +2 -1
- package/src/web/vitepress/process-front-matter-files.ts +15 -7
- package/lib/tsconfig.test.tsbuildinfo +0 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { afterAll, beforeAll, it } from "vitest";
|
|
3
|
+
import { alchemy } from "../alchemy.ts";
|
|
4
|
+
import { R2RestStateStore } from "../cloudflare/r2-rest-state-store.ts";
|
|
5
|
+
import { Scope } from "../scope.ts";
|
|
6
|
+
import type { StateStoreType } from "../state.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extend the Alchemy interface to include test functionality
|
|
10
|
+
*/
|
|
11
|
+
declare module "../alchemy.ts" {
|
|
12
|
+
interface Alchemy {
|
|
13
|
+
test: typeof test;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Add test functionality to alchemy instance
|
|
19
|
+
*/
|
|
20
|
+
alchemy.test = test;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for configuring test behavior
|
|
24
|
+
*/
|
|
25
|
+
export interface TestOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Whether to suppress logging output.
|
|
28
|
+
* @default false.
|
|
29
|
+
*/
|
|
30
|
+
quiet?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Password to use for test resources.
|
|
34
|
+
* @default "test-password".
|
|
35
|
+
*/
|
|
36
|
+
password?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Override the default state store for the test.
|
|
40
|
+
*/
|
|
41
|
+
stateStore?: StateStoreType;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Prefix to use for the scope to isolate tests and environments.
|
|
45
|
+
*/
|
|
46
|
+
prefix?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Test function type definition with overloads
|
|
51
|
+
*/
|
|
52
|
+
type test = {
|
|
53
|
+
/**
|
|
54
|
+
* Create a test with default options
|
|
55
|
+
* @param name Test name
|
|
56
|
+
* @param fn Test function
|
|
57
|
+
* @param timeout Optional timeout in milliseconds
|
|
58
|
+
*/
|
|
59
|
+
(name: string, fn: (scope: Scope) => Promise<any>, timeout?: number): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a test with custom options
|
|
63
|
+
* @param name Test name
|
|
64
|
+
* @param options Test configuration options
|
|
65
|
+
* @param fn Test function
|
|
66
|
+
* @param timeout Optional timeout in milliseconds
|
|
67
|
+
*/
|
|
68
|
+
(
|
|
69
|
+
name: string,
|
|
70
|
+
options: TestOptions,
|
|
71
|
+
fn: (scope: Scope) => Promise<any>,
|
|
72
|
+
timeout?: number,
|
|
73
|
+
): void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Skip test conditionally
|
|
77
|
+
* @param condition If true, test will be skipped
|
|
78
|
+
*/
|
|
79
|
+
skipIf(condition: boolean): test;
|
|
80
|
+
|
|
81
|
+
beforeAll(fn: (scope: Scope) => Promise<void>): void;
|
|
82
|
+
|
|
83
|
+
afterAll(fn: (scope: Scope) => Promise<void>): void;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Current test scope
|
|
87
|
+
*/
|
|
88
|
+
scope: Scope;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Creates a test helper function that provides scoped resource management
|
|
93
|
+
*
|
|
94
|
+
* @param meta Import meta object from the test file
|
|
95
|
+
* @param defaultOptions Default options to apply to all tests
|
|
96
|
+
* @returns Test function with scope management
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const test = alchemy.test(import.meta, {
|
|
101
|
+
prefix: BRANCH_PREFIX
|
|
102
|
+
});
|
|
103
|
+
*
|
|
104
|
+
* describe("My Resource", () => {
|
|
105
|
+
* test("create and delete", async (scope) => {
|
|
106
|
+
* try {
|
|
107
|
+
* const resource = await MyResource("test", { ... });
|
|
108
|
+
* expect(resource.id).toBeTruthy();
|
|
109
|
+
* } finally {
|
|
110
|
+
* await alchemy.destroy(scope);
|
|
111
|
+
* }
|
|
112
|
+
* });
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export function test(meta: ImportMeta, defaultOptions?: TestOptions): test {
|
|
117
|
+
defaultOptions = defaultOptions ?? {};
|
|
118
|
+
if (
|
|
119
|
+
defaultOptions.stateStore === undefined &&
|
|
120
|
+
process.env.ALCHEMY_STATE_STORE === "cloudflare"
|
|
121
|
+
) {
|
|
122
|
+
defaultOptions.stateStore = (scope) =>
|
|
123
|
+
new R2RestStateStore(scope, {
|
|
124
|
+
apiKey: alchemy.secret(process.env.CLOUDFLARE_API_KEY),
|
|
125
|
+
email: process.env.CLOUDFLARE_EMAIL,
|
|
126
|
+
bucketName: process.env.CLOUDFLARE_BUCKET_NAME!,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
test.skipIf = (condition: boolean) => {
|
|
131
|
+
if (condition) {
|
|
132
|
+
return (..._args: any[]) => {};
|
|
133
|
+
}
|
|
134
|
+
return test;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const scope = new Scope({
|
|
138
|
+
scopeName: `${defaultOptions.prefix ? `${defaultOptions.prefix}-` : ""}${path.basename(meta.filename)}`,
|
|
139
|
+
stateStore: defaultOptions?.stateStore,
|
|
140
|
+
phase: "up",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test.beforeAll = (fn: (scope: Scope) => Promise<void>) => {
|
|
144
|
+
return beforeAll(() => scope.run(() => fn(scope)));
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
test.afterAll = (fn: (scope: Scope) => Promise<void>) => {
|
|
148
|
+
return afterAll(() => scope.run(() => fn(scope)));
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return test as test;
|
|
152
|
+
|
|
153
|
+
function test(
|
|
154
|
+
...args:
|
|
155
|
+
| [
|
|
156
|
+
name: string,
|
|
157
|
+
options: TestOptions,
|
|
158
|
+
fn: (scope: Scope) => Promise<void>,
|
|
159
|
+
]
|
|
160
|
+
| [name: string, fn: (scope: Scope) => Promise<void>]
|
|
161
|
+
) {
|
|
162
|
+
const testName = args[0];
|
|
163
|
+
const _options = typeof args[1] === "object" ? args[1] : undefined;
|
|
164
|
+
const timeout =
|
|
165
|
+
typeof args[args.length - 1] === "number"
|
|
166
|
+
? (args[args.length - 1] as number)
|
|
167
|
+
: 120000;
|
|
168
|
+
const spread = (obj: any) =>
|
|
169
|
+
obj && typeof obj === "object"
|
|
170
|
+
? Object.fromEntries(
|
|
171
|
+
Object.entries(obj).flatMap(([k, v]) =>
|
|
172
|
+
v !== undefined ? [[k, v]] : [],
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
: {};
|
|
176
|
+
|
|
177
|
+
const options: TestOptions = {
|
|
178
|
+
quiet: false,
|
|
179
|
+
password: "test-password",
|
|
180
|
+
...spread(defaultOptions),
|
|
181
|
+
...spread(_options),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const fn = typeof args[1] === "function" ? args[1] : args[2]!;
|
|
185
|
+
|
|
186
|
+
return it(
|
|
187
|
+
testName,
|
|
188
|
+
(ctx) => {
|
|
189
|
+
// Get the current describe block name from the test context
|
|
190
|
+
let describeBlockName = "";
|
|
191
|
+
if (ctx.task.suite?.name) {
|
|
192
|
+
describeBlockName = `${ctx.task.suite.name}/`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return alchemy.run(
|
|
196
|
+
`${describeBlockName}${testName}`,
|
|
197
|
+
{
|
|
198
|
+
...options,
|
|
199
|
+
parent: scope,
|
|
200
|
+
},
|
|
201
|
+
async (scope) => {
|
|
202
|
+
await scope.run(() => fn(scope));
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
},
|
|
206
|
+
timeout,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
package/src/upstash/api.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Secret } from "../secret.ts";
|
|
2
|
+
import { safeFetch } from "../util/safe-fetch.ts";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Options for Upstash API requests
|
|
@@ -68,7 +69,7 @@ export class UpstashApi {
|
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
return
|
|
72
|
+
return safeFetch(`${this.baseUrl}${path}`, {
|
|
72
73
|
...init,
|
|
73
74
|
headers,
|
|
74
75
|
});
|
package/src/util/retry.ts
CHANGED
|
@@ -21,11 +21,10 @@ export async function withExponentialBackoff<T>(
|
|
|
21
21
|
while (true) {
|
|
22
22
|
try {
|
|
23
23
|
return await operation();
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.log(
|
|
24
|
+
} catch (error: any) {
|
|
25
|
+
console.log(error.message);
|
|
26
26
|
attempt++;
|
|
27
27
|
if (attempt >= maxAttempts || !isRetryable(error)) {
|
|
28
|
-
console.warn("Failed after ", attempt, " attempts");
|
|
29
28
|
throw error;
|
|
30
29
|
}
|
|
31
30
|
|
|
@@ -33,6 +32,7 @@ export async function withExponentialBackoff<T>(
|
|
|
33
32
|
const jitter = Math.random() * 0.1 * delay;
|
|
34
33
|
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
35
34
|
delay *= 2; // Double the delay for next attempt
|
|
35
|
+
delay = Math.min(delay, 10000); // Cap at 10 seconds
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Agent } from "undici";
|
|
2
|
+
|
|
3
|
+
export async function safeFetch(
|
|
4
|
+
url: string | URL | Request,
|
|
5
|
+
options: RequestInit = {},
|
|
6
|
+
retries = 3,
|
|
7
|
+
) {
|
|
8
|
+
let latestErr: any;
|
|
9
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
10
|
+
try {
|
|
11
|
+
return await fetch(url, {
|
|
12
|
+
...options,
|
|
13
|
+
// Type assertion to work around Cloudflare Workers type definitions
|
|
14
|
+
dispatcher: await dispatcher(),
|
|
15
|
+
} as RequestInit);
|
|
16
|
+
} catch (err: any) {
|
|
17
|
+
latestErr = err;
|
|
18
|
+
const shouldRetry =
|
|
19
|
+
err?.code === "UND_ERR_SOCKET" ||
|
|
20
|
+
err?.code === "ECONNRESET" ||
|
|
21
|
+
err?.name === "FetchError";
|
|
22
|
+
|
|
23
|
+
if (!shouldRetry || attempt === retries) {
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.warn(`Retry ${attempt}/${retries} for ${url}: ${err.code}`);
|
|
28
|
+
await new Promise((r) => setTimeout(r, 250 * attempt)); // exponential backoff
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw latestErr;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let agent:
|
|
35
|
+
| {
|
|
36
|
+
value: Agent | undefined;
|
|
37
|
+
}
|
|
38
|
+
| undefined;
|
|
39
|
+
|
|
40
|
+
async function dispatcher() {
|
|
41
|
+
if (agent) {
|
|
42
|
+
return agent.value;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
// undici not available in all runtimes
|
|
46
|
+
const { Agent } = await import("undici");
|
|
47
|
+
|
|
48
|
+
if (!agent) {
|
|
49
|
+
// Configure agent with connection limits to avoid socket errors
|
|
50
|
+
agent = {
|
|
51
|
+
value: new Agent({
|
|
52
|
+
pipelining: 0, // Disable pipelining to avoid connection issues
|
|
53
|
+
// connections: 5, // Limit concurrent connections per host
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
agent = { value: undefined };
|
|
59
|
+
}
|
|
60
|
+
return agent.value;
|
|
61
|
+
}
|
package/src/vercel/api.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { alchemy } from "../alchemy.ts";
|
|
2
2
|
import type { Secret } from "../secret.ts";
|
|
3
|
+
import { safeFetch } from "../util/safe-fetch.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Options for Vercel API requests
|
|
@@ -85,7 +86,7 @@ export class VercelApi {
|
|
|
85
86
|
delete headers["Content-Type"];
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
const response = await
|
|
89
|
+
const response = await safeFetch(`${this.baseUrl}${path}`, {
|
|
89
90
|
...init,
|
|
90
91
|
headers,
|
|
91
92
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Process markdown files with frontmatter to generate navigation items
|
|
@@ -56,14 +57,21 @@ export async function processFrontmatterFiles(
|
|
|
56
57
|
let title;
|
|
57
58
|
|
|
58
59
|
if (frontmatterMatch) {
|
|
59
|
-
const frontmatter = frontmatterMatch[1];
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const frontmatter = parseYaml(frontmatterMatch[1]);
|
|
61
|
+
|
|
62
|
+
// Parse order as a number (supports both integers and floats)
|
|
63
|
+
if (frontmatter.order !== undefined) {
|
|
64
|
+
if (typeof frontmatter.order !== "number") {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Invalid order field in ${fullPath}: expected number, got ${typeof frontmatter.order}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
order = frontmatter.order;
|
|
63
70
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
|
|
72
|
+
// Get title from frontmatter
|
|
73
|
+
if (frontmatter.title) {
|
|
74
|
+
title = String(frontmatter.title).trim();
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
77
|
|