@checkstack/scripts 0.1.1 → 0.2.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/package.json CHANGED
@@ -1,19 +1,40 @@
1
1
  {
2
2
  "name": "@checkstack/scripts",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
+ "description": "Checkstack tooling: plugin scaffolding, codegen, and the plugin-pack CLI used by external plugin authors.",
5
+ "license": "Elastic-2.0",
6
+ "type": "module",
7
+ "checkstack": {
8
+ "type": "tooling"
9
+ },
4
10
  "bin": {
5
11
  "checkstack-scripts": "./src/cli.ts"
6
12
  },
13
+ "files": [
14
+ "src",
15
+ "README.md"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
7
20
  "scripts": {
8
21
  "sync": "bun run src/sync.ts",
9
- "typecheck": "tsc --noEmit"
22
+ "typecheck": "tsgo -b"
10
23
  },
11
24
  "dependencies": {
12
- "inquirer": "^8.1.0",
13
- "handlebars": "^4.7.8"
25
+ "@checkstack/common": "0.7.0",
26
+ "@checkstack/backend": "0.8.2",
27
+ "@checkstack/frontend": "0.4.2",
28
+ "@checkstack/frontend-api": "0.4.1",
29
+ "@checkstack/ui": "1.7.0",
30
+ "@vitejs/plugin-react": "^4.3.4",
31
+ "inquirer": "^13.4.1",
32
+ "handlebars": "^4.7.8",
33
+ "tar": "^7.4.3",
34
+ "vite": "^5.4.0"
14
35
  },
15
36
  "devDependencies": {
16
- "@checkstack/tsconfig": "0.0.2",
37
+ "@checkstack/tsconfig": "0.0.6",
17
38
  "@types/inquirer": "^8.2.10",
18
39
  "@types/handlebars": "^4.1.0",
19
40
  "typescript": "^5.0.0"
package/src/cli.ts CHANGED
@@ -7,11 +7,13 @@ const command = process.argv[2];
7
7
  if (!command) {
8
8
  console.log("Usage: checkstack-scripts <command>");
9
9
  console.log("\nCommands:");
10
- console.log(" create - Create a new plugin interactively");
11
- console.log(" sync - Synchronize package configurations");
12
- console.log(" generate - Generate migrations and strip public schema");
13
- console.log(" typecheck - Run TypeScript type checking");
14
- console.log(" lint - Run linting checks");
10
+ console.log(" create - Create a new plugin interactively");
11
+ console.log(" sync - Synchronize package configurations");
12
+ console.log(" generate - Generate migrations and strip public schema");
13
+ console.log(" plugin-pack - Pack a plugin (or bundle) into a .tgz for distribution");
14
+ console.log(" dev - Run a local Checkstack dev server with the current directory's plugin loaded");
15
+ console.log(" typecheck - Run TypeScript type checking");
16
+ console.log(" lint - Run linting checks");
15
17
  process.exit(1);
16
18
  }
17
19
 
@@ -51,6 +53,30 @@ if (command === "generate") {
51
53
  process.exit(result.status ?? 0);
52
54
  }
53
55
 
56
+ if (command === "plugin-pack") {
57
+ // Dispatch to the plugin-pack module. We invoke it via `bun run` so it
58
+ // works whether the user runs `bunx @checkstack/scripts plugin-pack`
59
+ // (resolves to node_modules), `bun run cli plugin-pack`
60
+ // (relative path from repo), or via the bin script.
61
+ const scriptPath = path.resolve(
62
+ new URL("commands/plugin-pack.ts", import.meta.url).pathname,
63
+ );
64
+ const result = spawnSync("bun", ["run", scriptPath, ...process.argv.slice(3)], {
65
+ stdio: "inherit",
66
+ });
67
+ process.exit(result.status ?? 0);
68
+ }
69
+
70
+ if (command === "dev") {
71
+ const scriptPath = path.resolve(
72
+ new URL("commands/dev-server.ts", import.meta.url).pathname,
73
+ );
74
+ const result = spawnSync("bun", ["run", scriptPath, ...process.argv.slice(3)], {
75
+ stdio: "inherit",
76
+ });
77
+ process.exit(result.status ?? 0);
78
+ }
79
+
54
80
  // Fallback for other scripts if we want to centralize their execution logic
55
81
  console.error(`Unknown command: ${command}`);
56
82
  process.exit(1);
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import inquirer from "inquirer";
3
3
  import path from "node:path";
4
+ import { spawnSync } from "node:child_process";
4
5
  import {
5
6
  validatePluginName,
6
7
  pluginExists,
@@ -217,6 +218,22 @@ export async function createCommand() {
217
218
  }
218
219
 
219
220
  // Success message with next steps
221
+ // Refresh project-references in every package's tsconfig + the root
222
+ // solution tsconfig so the new package is wired into the typecheck
223
+ // graph. Affects only tsconfig.json files; safe to rerun any time.
224
+ console.log("\n🔗 Refreshing TypeScript project references...");
225
+ const refResult = spawnSync(
226
+ "bun",
227
+ ["run", "typecheck:references:generate"],
228
+ { stdio: "inherit" },
229
+ );
230
+ if (refResult.status !== 0) {
231
+ console.warn(
232
+ "⚠️ Failed to refresh references automatically. " +
233
+ "Run `bun run typecheck:references:generate` manually before typechecking.",
234
+ );
235
+ }
236
+
220
237
  console.log(
221
238
  `\n✅ ${
222
239
  locationLabel.charAt(0).toUpperCase() + locationLabel.slice(1)
@@ -0,0 +1,411 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import path from "node:path";
3
+ import { resolveCorePluginDeps } from "./dev-deps-resolver";
4
+
5
+ /**
6
+ * In-memory fixture for a virtual node_modules tree. Maps a fully
7
+ * qualified file path to its file contents (typically package.json
8
+ * stringified JSON).
9
+ */
10
+ type Fs = Record<string, string>;
11
+
12
+ const ROOT = "/plugin-author/repo";
13
+ const NM = `${ROOT}/node_modules`;
14
+
15
+ function makeFs(packages: Record<string, unknown>): Fs {
16
+ const out: Fs = {};
17
+ for (const [name, pkg] of Object.entries(packages)) {
18
+ out[`${NM}/${name}/package.json`] = JSON.stringify(pkg);
19
+ }
20
+ return out;
21
+ }
22
+
23
+ function makeResolver(fs: Fs) {
24
+ return (_from: string, request: string): string | undefined => {
25
+ // Only handle requests of the form `<pkg>/package.json` — that's all
26
+ // resolveCorePluginDeps issues. A real `createRequire` resolves any
27
+ // entry, but we don't need that fidelity here.
28
+ if (!request.endsWith("/package.json")) return undefined;
29
+ const pkgName = request.replace(/\/package\.json$/, "");
30
+ const candidate = `${NM}/${pkgName}/package.json`;
31
+ return fs[candidate] === undefined ? undefined : candidate;
32
+ };
33
+ }
34
+
35
+ function readFileFromFs(fs: Fs) {
36
+ return (p: string): string => {
37
+ const text = fs[p];
38
+ if (text === undefined) throw new Error(`ENOENT: ${p}`);
39
+ return text;
40
+ };
41
+ }
42
+
43
+ describe("resolveCorePluginDeps", () => {
44
+ it("returns only the auto-included providers for a plugin with no @checkstack/* backend deps", () => {
45
+ const fs: Fs = {
46
+ [`${ROOT}/package.json`]: JSON.stringify({
47
+ name: "@my-org/widget-backend",
48
+ dependencies: {
49
+ "@checkstack/backend-api": "^1.0.0",
50
+ lodash: "^4.0.0",
51
+ },
52
+ checkstack: { type: "backend", pluginId: "widget" },
53
+ }),
54
+ ...makeFs({
55
+ "@checkstack/backend-api": {
56
+ name: "@checkstack/backend-api",
57
+ checkstack: { type: "common" },
58
+ },
59
+ "@checkstack/queue-memory-backend": {
60
+ name: "@checkstack/queue-memory-backend",
61
+ main: "src/index.ts",
62
+ checkstack: { type: "backend", pluginId: "queue-memory" },
63
+ },
64
+ "@checkstack/cache-memory-backend": {
65
+ name: "@checkstack/cache-memory-backend",
66
+ main: "src/index.ts",
67
+ checkstack: { type: "backend", pluginId: "cache-memory" },
68
+ },
69
+ }),
70
+ };
71
+
72
+ const resolved = resolveCorePluginDeps({
73
+ pluginDir: ROOT,
74
+ readFile: readFileFromFs(fs),
75
+ resolveFrom: makeResolver(fs),
76
+ });
77
+
78
+ const names = resolved.map((r) => r.name).toSorted();
79
+ expect(names).toEqual([
80
+ "@checkstack/cache-memory-backend",
81
+ "@checkstack/queue-memory-backend",
82
+ ]);
83
+ });
84
+
85
+ it("includes a single backend dep declared in dependencies", () => {
86
+ const fs: Fs = {
87
+ [`${ROOT}/package.json`]: JSON.stringify({
88
+ name: "@my-org/healthcheck-rcon-backend",
89
+ dependencies: {
90
+ "@checkstack/healthcheck-backend": "^1.0.0",
91
+ },
92
+ checkstack: { type: "backend", pluginId: "healthcheck-rcon" },
93
+ }),
94
+ ...makeFs({
95
+ "@checkstack/healthcheck-backend": {
96
+ name: "@checkstack/healthcheck-backend",
97
+ main: "src/index.ts",
98
+ checkstack: { type: "backend", pluginId: "healthcheck" },
99
+ dependencies: {},
100
+ },
101
+ "@checkstack/queue-memory-backend": {
102
+ name: "@checkstack/queue-memory-backend",
103
+ main: "src/index.ts",
104
+ checkstack: { type: "backend", pluginId: "queue-memory" },
105
+ },
106
+ "@checkstack/cache-memory-backend": {
107
+ name: "@checkstack/cache-memory-backend",
108
+ main: "src/index.ts",
109
+ checkstack: { type: "backend", pluginId: "cache-memory" },
110
+ },
111
+ }),
112
+ };
113
+
114
+ const resolved = resolveCorePluginDeps({
115
+ pluginDir: ROOT,
116
+ readFile: readFileFromFs(fs),
117
+ resolveFrom: makeResolver(fs),
118
+ });
119
+
120
+ expect(resolved.map((r) => r.name).toSorted()).toEqual([
121
+ "@checkstack/cache-memory-backend",
122
+ "@checkstack/healthcheck-backend",
123
+ "@checkstack/queue-memory-backend",
124
+ ]);
125
+
126
+ const hc = resolved.find((r) => r.name === "@checkstack/healthcheck-backend");
127
+ expect(hc?.modulePath).toBe(
128
+ path.resolve(`${NM}/@checkstack/healthcheck-backend/src/index.ts`),
129
+ );
130
+ });
131
+
132
+ it("walks transitive @checkstack/* backend deps", () => {
133
+ // Plugin → notification-discord-backend → notification-backend → catalog-backend
134
+ const fs: Fs = {
135
+ [`${ROOT}/package.json`]: JSON.stringify({
136
+ name: "@my-org/widget-backend",
137
+ dependencies: {
138
+ "@checkstack/notification-discord-backend": "^1.0.0",
139
+ },
140
+ checkstack: { type: "backend", pluginId: "widget" },
141
+ }),
142
+ ...makeFs({
143
+ "@checkstack/notification-discord-backend": {
144
+ name: "@checkstack/notification-discord-backend",
145
+ main: "src/index.ts",
146
+ checkstack: { type: "backend", pluginId: "notification-discord" },
147
+ dependencies: {
148
+ "@checkstack/notification-backend": "^1.0.0",
149
+ },
150
+ },
151
+ "@checkstack/notification-backend": {
152
+ name: "@checkstack/notification-backend",
153
+ main: "src/index.ts",
154
+ checkstack: { type: "backend", pluginId: "notification" },
155
+ dependencies: {
156
+ "@checkstack/catalog-backend": "^1.0.0",
157
+ },
158
+ },
159
+ "@checkstack/catalog-backend": {
160
+ name: "@checkstack/catalog-backend",
161
+ main: "src/index.ts",
162
+ checkstack: { type: "backend", pluginId: "catalog" },
163
+ },
164
+ "@checkstack/queue-memory-backend": {
165
+ name: "@checkstack/queue-memory-backend",
166
+ main: "src/index.ts",
167
+ checkstack: { type: "backend", pluginId: "queue-memory" },
168
+ },
169
+ "@checkstack/cache-memory-backend": {
170
+ name: "@checkstack/cache-memory-backend",
171
+ main: "src/index.ts",
172
+ checkstack: { type: "backend", pluginId: "cache-memory" },
173
+ },
174
+ }),
175
+ };
176
+
177
+ const resolved = resolveCorePluginDeps({
178
+ pluginDir: ROOT,
179
+ readFile: readFileFromFs(fs),
180
+ resolveFrom: makeResolver(fs),
181
+ });
182
+
183
+ expect(resolved.map((r) => r.name).toSorted()).toEqual([
184
+ "@checkstack/cache-memory-backend",
185
+ "@checkstack/catalog-backend",
186
+ "@checkstack/notification-backend",
187
+ "@checkstack/notification-discord-backend",
188
+ "@checkstack/queue-memory-backend",
189
+ ]);
190
+ });
191
+
192
+ it("walks through common-type packages to find transitive backend deps", () => {
193
+ // Plugin → my-shared-common (type: common) → healthcheck-backend
194
+ // (Unusual but possible; common packages can list runtime deps.)
195
+ const fs: Fs = {
196
+ [`${ROOT}/package.json`]: JSON.stringify({
197
+ name: "@my-org/widget-backend",
198
+ dependencies: {
199
+ "@checkstack/widget-common": "^1.0.0",
200
+ },
201
+ checkstack: { type: "backend", pluginId: "widget" },
202
+ }),
203
+ ...makeFs({
204
+ "@checkstack/widget-common": {
205
+ name: "@checkstack/widget-common",
206
+ checkstack: { type: "common" },
207
+ dependencies: {
208
+ "@checkstack/healthcheck-backend": "^1.0.0",
209
+ },
210
+ },
211
+ "@checkstack/healthcheck-backend": {
212
+ name: "@checkstack/healthcheck-backend",
213
+ main: "src/index.ts",
214
+ checkstack: { type: "backend", pluginId: "healthcheck" },
215
+ },
216
+ "@checkstack/queue-memory-backend": {
217
+ name: "@checkstack/queue-memory-backend",
218
+ main: "src/index.ts",
219
+ checkstack: { type: "backend", pluginId: "queue-memory" },
220
+ },
221
+ "@checkstack/cache-memory-backend": {
222
+ name: "@checkstack/cache-memory-backend",
223
+ main: "src/index.ts",
224
+ checkstack: { type: "backend", pluginId: "cache-memory" },
225
+ },
226
+ }),
227
+ };
228
+
229
+ const resolved = resolveCorePluginDeps({
230
+ pluginDir: ROOT,
231
+ readFile: readFileFromFs(fs),
232
+ resolveFrom: makeResolver(fs),
233
+ });
234
+
235
+ expect(resolved.map((r) => r.name)).toContain(
236
+ "@checkstack/healthcheck-backend",
237
+ );
238
+ // The common pkg itself isn't loaded as a plugin
239
+ expect(resolved.map((r) => r.name)).not.toContain(
240
+ "@checkstack/widget-common",
241
+ );
242
+ });
243
+
244
+ it("excludes the plugin under dev itself even when listed transitively", () => {
245
+ const fs: Fs = {
246
+ [`${ROOT}/package.json`]: JSON.stringify({
247
+ name: "@my-org/widget-backend",
248
+ dependencies: {
249
+ "@checkstack/healthcheck-backend": "^1.0.0",
250
+ },
251
+ checkstack: { type: "backend", pluginId: "widget" },
252
+ }),
253
+ ...makeFs({
254
+ "@checkstack/healthcheck-backend": {
255
+ name: "@checkstack/healthcheck-backend",
256
+ main: "src/index.ts",
257
+ checkstack: { type: "backend", pluginId: "healthcheck" },
258
+ // Pretend the platform plugin pulls back into the user's plugin
259
+ // (this would never happen in practice, but the resolver must
260
+ // never try to load the plugin under dev as a sibling).
261
+ dependencies: {
262
+ "@my-org/widget-backend": "^1.0.0",
263
+ },
264
+ },
265
+ "@checkstack/queue-memory-backend": {
266
+ name: "@checkstack/queue-memory-backend",
267
+ main: "src/index.ts",
268
+ checkstack: { type: "backend", pluginId: "queue-memory" },
269
+ },
270
+ "@checkstack/cache-memory-backend": {
271
+ name: "@checkstack/cache-memory-backend",
272
+ main: "src/index.ts",
273
+ checkstack: { type: "backend", pluginId: "cache-memory" },
274
+ },
275
+ }),
276
+ };
277
+
278
+ const resolved = resolveCorePluginDeps({
279
+ pluginDir: ROOT,
280
+ readFile: readFileFromFs(fs),
281
+ resolveFrom: makeResolver(fs),
282
+ });
283
+ expect(resolved.map((r) => r.name)).not.toContain("@my-org/widget-backend");
284
+ });
285
+
286
+ it("skips queue-memory auto-include when bullmq is already in the dep graph", () => {
287
+ const fs: Fs = {
288
+ [`${ROOT}/package.json`]: JSON.stringify({
289
+ name: "@my-org/widget-backend",
290
+ dependencies: {
291
+ "@checkstack/queue-bullmq-backend": "^1.0.0",
292
+ },
293
+ checkstack: { type: "backend", pluginId: "widget" },
294
+ }),
295
+ ...makeFs({
296
+ "@checkstack/queue-bullmq-backend": {
297
+ name: "@checkstack/queue-bullmq-backend",
298
+ main: "src/index.ts",
299
+ checkstack: { type: "backend", pluginId: "queue-bullmq" },
300
+ },
301
+ "@checkstack/queue-memory-backend": {
302
+ name: "@checkstack/queue-memory-backend",
303
+ main: "src/index.ts",
304
+ checkstack: { type: "backend", pluginId: "queue-memory" },
305
+ },
306
+ "@checkstack/cache-memory-backend": {
307
+ name: "@checkstack/cache-memory-backend",
308
+ main: "src/index.ts",
309
+ checkstack: { type: "backend", pluginId: "cache-memory" },
310
+ },
311
+ }),
312
+ };
313
+
314
+ const resolved = resolveCorePluginDeps({
315
+ pluginDir: ROOT,
316
+ readFile: readFileFromFs(fs),
317
+ resolveFrom: makeResolver(fs),
318
+ });
319
+ const names = resolved.map((r) => r.name);
320
+ expect(names).toContain("@checkstack/queue-bullmq-backend");
321
+ expect(names).not.toContain("@checkstack/queue-memory-backend");
322
+ });
323
+
324
+ it("does not include @checkstack/* frontend or tooling packages", () => {
325
+ const fs: Fs = {
326
+ [`${ROOT}/package.json`]: JSON.stringify({
327
+ name: "@my-org/widget-backend",
328
+ dependencies: {
329
+ "@checkstack/healthcheck-backend": "^1.0.0",
330
+ "@checkstack/healthcheck-frontend": "^1.0.0",
331
+ "@checkstack/scripts": "^0.1.0",
332
+ },
333
+ checkstack: { type: "backend", pluginId: "widget" },
334
+ }),
335
+ ...makeFs({
336
+ "@checkstack/healthcheck-backend": {
337
+ name: "@checkstack/healthcheck-backend",
338
+ main: "src/index.ts",
339
+ checkstack: { type: "backend", pluginId: "healthcheck" },
340
+ },
341
+ "@checkstack/healthcheck-frontend": {
342
+ name: "@checkstack/healthcheck-frontend",
343
+ main: "src/index.tsx",
344
+ checkstack: { type: "frontend", pluginId: "healthcheck" },
345
+ },
346
+ "@checkstack/scripts": {
347
+ name: "@checkstack/scripts",
348
+ checkstack: { type: "tooling" },
349
+ },
350
+ "@checkstack/queue-memory-backend": {
351
+ name: "@checkstack/queue-memory-backend",
352
+ main: "src/index.ts",
353
+ checkstack: { type: "backend", pluginId: "queue-memory" },
354
+ },
355
+ "@checkstack/cache-memory-backend": {
356
+ name: "@checkstack/cache-memory-backend",
357
+ main: "src/index.ts",
358
+ checkstack: { type: "backend", pluginId: "cache-memory" },
359
+ },
360
+ }),
361
+ };
362
+
363
+ const resolved = resolveCorePluginDeps({
364
+ pluginDir: ROOT,
365
+ readFile: readFileFromFs(fs),
366
+ resolveFrom: makeResolver(fs),
367
+ });
368
+ const names = resolved.map((r) => r.name);
369
+ expect(names).toContain("@checkstack/healthcheck-backend");
370
+ expect(names).not.toContain("@checkstack/healthcheck-frontend");
371
+ expect(names).not.toContain("@checkstack/scripts");
372
+ });
373
+
374
+ it("silently skips a declared dep that isn't actually installed", () => {
375
+ const fs: Fs = {
376
+ [`${ROOT}/package.json`]: JSON.stringify({
377
+ name: "@my-org/widget-backend",
378
+ dependencies: {
379
+ "@checkstack/missing-backend": "^1.0.0",
380
+ },
381
+ checkstack: { type: "backend", pluginId: "widget" },
382
+ }),
383
+ ...makeFs({
384
+ "@checkstack/queue-memory-backend": {
385
+ name: "@checkstack/queue-memory-backend",
386
+ main: "src/index.ts",
387
+ checkstack: { type: "backend", pluginId: "queue-memory" },
388
+ },
389
+ "@checkstack/cache-memory-backend": {
390
+ name: "@checkstack/cache-memory-backend",
391
+ main: "src/index.ts",
392
+ checkstack: { type: "backend", pluginId: "cache-memory" },
393
+ },
394
+ }),
395
+ };
396
+
397
+ const resolved = resolveCorePluginDeps({
398
+ pluginDir: ROOT,
399
+ readFile: readFileFromFs(fs),
400
+ resolveFrom: makeResolver(fs),
401
+ });
402
+ expect(resolved.map((r) => r.name)).not.toContain(
403
+ "@checkstack/missing-backend",
404
+ );
405
+ // Auto-included providers still present — boot needs them.
406
+ expect(resolved.map((r) => r.name).toSorted()).toEqual([
407
+ "@checkstack/cache-memory-backend",
408
+ "@checkstack/queue-memory-backend",
409
+ ]);
410
+ });
411
+ });