draftpkg 0.0.0 → 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.
- package/README.md +26 -0
- package/package.json +9 -1
- package/src/__tests__/setup.test.ts +0 -115
- package/src/cli.ts +0 -39
- package/src/executor.ts +0 -8
- package/src/setup.ts +0 -38
- package/tsconfig.json +0 -10
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# draftpkg
|
|
2
|
+
|
|
3
|
+
CLI tool for [Draftpkg](https://github.com/draftpkg/draftpkg) — a self-hosted drop-in replacement for CodeSandbox CI that automatically builds npm packages from pull requests and publishes them to a private registry.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx draftpkg setup
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This deploys a Cloudflare Worker that serves as your private npm registry. It will:
|
|
12
|
+
|
|
13
|
+
1. Create a KV namespace for metadata
|
|
14
|
+
2. Create an R2 bucket for tarballs
|
|
15
|
+
3. Deploy the Worker
|
|
16
|
+
4. Generate an API key
|
|
17
|
+
|
|
18
|
+
**Prerequisites:** a [Cloudflare account](https://dash.cloudflare.com/sign-up) and [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) logged in (`wrangler login`).
|
|
19
|
+
|
|
20
|
+
## What's next?
|
|
21
|
+
|
|
22
|
+
After setup, add the [Draftpkg GitHub Action](https://github.com/draftpkg/action) to your repositories. See the [full documentation](https://github.com/draftpkg/draftpkg) for details.
|
|
23
|
+
|
|
24
|
+
## License
|
|
25
|
+
|
|
26
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "draftpkg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/sarahdayan/draftpkg",
|
|
8
|
+
"directory": "packages/cli"
|
|
9
|
+
},
|
|
5
10
|
"bin": {
|
|
6
11
|
"draftpkg": "dist/cli.mjs"
|
|
7
12
|
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
8
16
|
"scripts": {
|
|
9
17
|
"build": "esbuild src/cli.ts --bundle --platform=node --target=node20 --format=esm --outfile=dist/cli.mjs",
|
|
10
18
|
"check-types": "tsc --noEmit",
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import type { ExecResult, Executor } from '../executor';
|
|
4
|
-
import { setup } from '../setup';
|
|
5
|
-
|
|
6
|
-
describe('setup', () => {
|
|
7
|
-
let executor: ReturnType<typeof createFakeExecutor>;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
executor = createFakeExecutor({
|
|
11
|
-
'wrangler whoami': 'You are logged in as test@example.com',
|
|
12
|
-
'wrangler kv namespace create METADATA': 'id = "kv-namespace-id-123"',
|
|
13
|
-
'wrangler r2 bucket create draftpkg-tarballs':
|
|
14
|
-
'Created bucket draftpkg-tarballs',
|
|
15
|
-
'wrangler deploy': 'Deployed to https://draftpkg-worker.test.workers.dev',
|
|
16
|
-
'wrangler secret put API_KEY': 'Success',
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('checks wrangler login', async () => {
|
|
21
|
-
await setup(executor);
|
|
22
|
-
|
|
23
|
-
expect(executor.calls[0]!.command).toBe('wrangler whoami');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('creates a KV namespace', async () => {
|
|
27
|
-
await setup(executor);
|
|
28
|
-
|
|
29
|
-
expect(
|
|
30
|
-
executor.calls.some((c) =>
|
|
31
|
-
c.command.includes('kv namespace create METADATA'),
|
|
32
|
-
),
|
|
33
|
-
).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('creates an R2 bucket', async () => {
|
|
37
|
-
await setup(executor);
|
|
38
|
-
|
|
39
|
-
expect(
|
|
40
|
-
executor.calls.some((c) =>
|
|
41
|
-
c.command.includes('r2 bucket create draftpkg-tarballs'),
|
|
42
|
-
),
|
|
43
|
-
).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('deploys the worker', async () => {
|
|
47
|
-
await setup(executor);
|
|
48
|
-
|
|
49
|
-
expect(
|
|
50
|
-
executor.calls.some((c) => c.command.includes('wrangler deploy')),
|
|
51
|
-
).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('sets the API key as a secret', async () => {
|
|
55
|
-
await setup(executor);
|
|
56
|
-
|
|
57
|
-
expect(
|
|
58
|
-
executor.calls.some((c) => c.command.includes('wrangler secret put API_KEY')),
|
|
59
|
-
).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('returns the worker URL and API key', async () => {
|
|
63
|
-
const result = await setup(executor);
|
|
64
|
-
|
|
65
|
-
expect(result.workerUrl).toMatch(/^https:\/\//);
|
|
66
|
-
expect(result.apiKey).toBeTruthy();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('throws if not logged in', async () => {
|
|
70
|
-
executor = createFakeExecutor({});
|
|
71
|
-
executor.run = async (command) => {
|
|
72
|
-
executor.calls.push({ command });
|
|
73
|
-
if (command === 'wrangler whoami') {
|
|
74
|
-
throw new Error('Not logged in');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return { stdout: '', stderr: '' };
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
await expect(setup(executor)).rejects.toThrow('logged in');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('runs steps in the correct order', async () => {
|
|
84
|
-
await setup(executor);
|
|
85
|
-
|
|
86
|
-
const commands = executor.calls.map((c) => c.command);
|
|
87
|
-
const whoamiIdx = commands.findIndex((c) => c.includes('whoami'));
|
|
88
|
-
const kvIdx = commands.findIndex((c) => c.includes('kv namespace create'));
|
|
89
|
-
const r2Idx = commands.findIndex((c) => c.includes('r2 bucket create'));
|
|
90
|
-
const deployIdx = commands.findIndex((c) => c.includes('wrangler deploy'));
|
|
91
|
-
|
|
92
|
-
expect(whoamiIdx).toBeLessThan(kvIdx);
|
|
93
|
-
expect(kvIdx).toBeLessThan(r2Idx);
|
|
94
|
-
expect(r2Idx).toBeLessThan(deployIdx);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
interface RecordedCall {
|
|
99
|
-
command: string;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function createFakeExecutor(
|
|
103
|
-
responses: Record<string, string> = {},
|
|
104
|
-
): Executor & { calls: RecordedCall[] } {
|
|
105
|
-
const calls: RecordedCall[] = [];
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
calls,
|
|
109
|
-
async run(command: string): Promise<ExecResult> {
|
|
110
|
-
calls.push({ command });
|
|
111
|
-
|
|
112
|
-
return { stdout: responses[command] ?? '', stderr: '' };
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
}
|
package/src/cli.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
|
|
4
|
-
import type { Executor } from "./executor";
|
|
5
|
-
import { setup } from "./setup";
|
|
6
|
-
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
|
|
9
|
-
function createExecutor(): Executor {
|
|
10
|
-
return {
|
|
11
|
-
async run(command) {
|
|
12
|
-
return execAsync(command);
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const command = process.argv[2];
|
|
18
|
-
|
|
19
|
-
if (command !== "setup") {
|
|
20
|
-
console.error(`Unknown command: ${command ?? "(none)"}`);
|
|
21
|
-
console.error("Usage: draftpkg setup");
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
console.log("Setting up Draftpkg...\n");
|
|
26
|
-
|
|
27
|
-
setup(createExecutor())
|
|
28
|
-
.then((result) => {
|
|
29
|
-
console.log("\nDraftpkg is ready!\n");
|
|
30
|
-
console.log(` Worker URL: ${result.workerUrl}`);
|
|
31
|
-
console.log(` API Key: ${result.apiKey}`);
|
|
32
|
-
console.log("\nAdd these as secrets in your GitHub repo:");
|
|
33
|
-
console.log(" DRAFTPKG_WORKER_URL");
|
|
34
|
-
console.log(" DRAFTPKG_API_KEY");
|
|
35
|
-
})
|
|
36
|
-
.catch((error) => {
|
|
37
|
-
console.error(error.message);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
});
|
package/src/executor.ts
DELETED
package/src/setup.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
|
|
3
|
-
import type { Executor } from "./executor";
|
|
4
|
-
|
|
5
|
-
export interface SetupResult {
|
|
6
|
-
workerUrl: string;
|
|
7
|
-
apiKey: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function setup(executor: Executor): Promise<SetupResult> {
|
|
11
|
-
// 1. Check login
|
|
12
|
-
try {
|
|
13
|
-
await executor.run("wrangler whoami");
|
|
14
|
-
} catch {
|
|
15
|
-
throw new Error(
|
|
16
|
-
"Not logged in to Wrangler. Run `wrangler login` first.",
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// 2. Create KV namespace
|
|
21
|
-
await executor.run("wrangler kv namespace create METADATA");
|
|
22
|
-
|
|
23
|
-
// 3. Create R2 bucket
|
|
24
|
-
await executor.run("wrangler r2 bucket create draftpkg-tarballs");
|
|
25
|
-
|
|
26
|
-
// 4. Deploy worker
|
|
27
|
-
const { stdout } = await executor.run("wrangler deploy");
|
|
28
|
-
const urlMatch = stdout.match(/https:\/\/[^\s]+\.workers\.dev/);
|
|
29
|
-
const workerUrl = urlMatch?.[0] ?? "";
|
|
30
|
-
|
|
31
|
-
// 5. Generate and set API key
|
|
32
|
-
const apiKey = crypto.randomUUID();
|
|
33
|
-
await executor.run(
|
|
34
|
-
`echo "${apiKey}" | wrangler secret put API_KEY`,
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return { workerUrl, apiKey };
|
|
38
|
-
}
|