@checkstack/scripts 0.1.2 → 0.3.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,22 +1,34 @@
1
1
  {
2
2
  "name": "@checkstack/scripts",
3
- "version": "0.1.2",
3
+ "version": "0.3.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",
4
7
  "checkstack": {
5
8
  "type": "tooling"
6
9
  },
7
10
  "bin": {
8
11
  "checkstack-scripts": "./src/cli.ts"
9
12
  },
13
+ "files": [
14
+ "src",
15
+ "README.md"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
10
20
  "scripts": {
11
21
  "sync": "bun run src/sync.ts",
12
- "typecheck": "tsc --noEmit"
22
+ "typecheck": "tsgo -b"
13
23
  },
14
24
  "dependencies": {
25
+ "@checkstack/common": "0.8.0",
15
26
  "inquirer": "^13.4.1",
16
- "handlebars": "^4.7.8"
27
+ "handlebars": "^4.7.8",
28
+ "tar": "^7.4.3"
17
29
  },
18
30
  "devDependencies": {
19
- "@checkstack/tsconfig": "0.0.3",
31
+ "@checkstack/tsconfig": "0.0.7",
20
32
  "@types/inquirer": "^8.2.10",
21
33
  "@types/handlebars": "^4.1.0",
22
34
  "typescript": "^5.0.0"
package/src/cli.ts CHANGED
@@ -7,11 +7,12 @@ 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(" typecheck - Run TypeScript type checking");
15
+ console.log(" lint - Run linting checks");
15
16
  process.exit(1);
16
17
  }
17
18
 
@@ -51,6 +52,20 @@ if (command === "generate") {
51
52
  process.exit(result.status ?? 0);
52
53
  }
53
54
 
55
+ if (command === "plugin-pack") {
56
+ // Dispatch to the plugin-pack module. We invoke it via `bun run` so it
57
+ // works whether the user runs `bunx @checkstack/scripts plugin-pack`
58
+ // (resolves to node_modules), `bun run cli plugin-pack`
59
+ // (relative path from repo), or via the bin script.
60
+ const scriptPath = path.resolve(
61
+ new URL("commands/plugin-pack.ts", import.meta.url).pathname,
62
+ );
63
+ const result = spawnSync("bun", ["run", scriptPath, ...process.argv.slice(3)], {
64
+ stdio: "inherit",
65
+ });
66
+ process.exit(result.status ?? 0);
67
+ }
68
+
54
69
  // Fallback for other scripts if we want to centralize their execution logic
55
70
  console.error(`Unknown command: ${command}`);
56
71
  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,397 @@
1
+ #!/usr/bin/env bun
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import { spawnSync } from "node:child_process";
6
+ import { create as tarCreate } from "tar";
7
+ import {
8
+ installPackageMetadataSchema,
9
+ pluginBundleManifestSchema,
10
+ type InstallPackageMetadata,
11
+ type PluginBundleManifest,
12
+ } from "@checkstack/common";
13
+
14
+ /**
15
+ * `plugin-pack` CLI.
16
+ *
17
+ * Two modes:
18
+ * - default (per-package): packs the cwd into a single `.tgz` matching what
19
+ * `bun pm pack` would produce, with extra metadata validation. This is
20
+ * what gets uploaded to npm.
21
+ * - `--bundle`: packs the cwd's primary package + every sibling listed in
22
+ * `package.json#checkstack.bundle`, then wraps them into an outer tarball
23
+ * containing `bundle.json` + `packages/<name>-<version>.tgz`. This is the
24
+ * artifact attached to a GitHub release / uploaded directly via the
25
+ * plugin manager UI.
26
+ *
27
+ * Workspace-resolution: `workspace:*` deps in any sibling's `package.json` are
28
+ * rewritten to the matching package's actual version before packing. Resolution
29
+ * walks the nearest ancestor `package.json` containing a `workspaces` field;
30
+ * if none is found we treat the cwd as standalone (no rewriting needed).
31
+ */
32
+
33
+ interface Args {
34
+ bundle: boolean;
35
+ outDir: string;
36
+ cwd: string;
37
+ validateOnly: boolean;
38
+ }
39
+
40
+ export async function runPluginPack(rawArgs: string[]): Promise<number> {
41
+ const args = parseArgs(rawArgs);
42
+
43
+ const pkg = readJson<InstallPackageMetadata>(
44
+ path.join(args.cwd, "package.json"),
45
+ );
46
+
47
+ // Validate metadata ABOVE pack so the user sees errors immediately.
48
+ const validation = installPackageMetadataSchema.safeParse(pkg);
49
+ if (!validation.success) {
50
+ console.error("āŒ package.json failed validation:");
51
+ for (const issue of validation.error.issues) {
52
+ console.error(` - ${issue.path.join(".")}: ${issue.message}`);
53
+ }
54
+ return 1;
55
+ }
56
+
57
+ if (args.validateOnly) {
58
+ console.log("āœ… Metadata valid.");
59
+ return 0;
60
+ }
61
+
62
+ // Pre-pack hooks
63
+ if (!args.bundle) {
64
+ runScriptIfPresent({ cwd: args.cwd, script: "typecheck" });
65
+ runScriptIfPresent({ cwd: args.cwd, script: "lint" });
66
+ }
67
+
68
+ fs.mkdirSync(args.outDir, { recursive: true });
69
+
70
+ if (args.bundle) {
71
+ const bundleNames = pkg.checkstack.bundle ?? [];
72
+ if (bundleNames.length === 0) {
73
+ console.error(
74
+ "āŒ --bundle requires `checkstack.bundle` to be set in package.json.",
75
+ );
76
+ return 1;
77
+ }
78
+
79
+ const workspaceMap = buildWorkspaceMap(args.cwd);
80
+
81
+ const primaryTarball = await packPackage({
82
+ pkgDir: args.cwd,
83
+ outDir: args.outDir,
84
+ workspaceMap,
85
+ });
86
+
87
+ const siblings: { name: string; version: string; tarball: string }[] = [
88
+ {
89
+ name: pkg.name,
90
+ version: pkg.version,
91
+ tarball: `packages/${path.basename(primaryTarball)}`,
92
+ },
93
+ ];
94
+
95
+ for (const sibName of bundleNames) {
96
+ if (sibName === pkg.name) continue;
97
+ const sibDir = workspaceMap.get(sibName);
98
+ if (!sibDir) {
99
+ console.error(
100
+ `āŒ Bundle sibling '${sibName}' not found in workspace. ` +
101
+ `Make sure it's a peer of the primary package.`,
102
+ );
103
+ return 1;
104
+ }
105
+ const sibTarball = await packPackage({
106
+ pkgDir: sibDir,
107
+ outDir: args.outDir,
108
+ workspaceMap,
109
+ });
110
+ const sibPkg = readJson<InstallPackageMetadata>(
111
+ path.join(sibDir, "package.json"),
112
+ );
113
+ siblings.push({
114
+ name: sibPkg.name,
115
+ version: sibPkg.version,
116
+ tarball: `packages/${path.basename(sibTarball)}`,
117
+ });
118
+ }
119
+
120
+ const manifest: PluginBundleManifest = pluginBundleManifestSchema.parse({
121
+ bundleVersion: 1,
122
+ primary: pkg.name,
123
+ packages: siblings,
124
+ });
125
+
126
+ const outerTarball = path.join(
127
+ args.outDir,
128
+ `${safeFileName(pkg.name)}-${pkg.version}-bundle.tgz`,
129
+ );
130
+
131
+ // Lay out files in a tmpdir then `tar.create`.
132
+ const stage = fs.mkdtempSync(path.join(os.tmpdir(), "checkstack-bundle-"));
133
+ try {
134
+ fs.mkdirSync(path.join(stage, "packages"), { recursive: true });
135
+ for (const sib of siblings) {
136
+ fs.copyFileSync(
137
+ path.join(args.outDir, path.basename(sib.tarball)),
138
+ path.join(stage, sib.tarball),
139
+ );
140
+ }
141
+ fs.writeFileSync(
142
+ path.join(stage, "bundle.json"),
143
+ JSON.stringify(manifest, undefined, 2),
144
+ );
145
+ await tarCreate(
146
+ {
147
+ gzip: true,
148
+ file: outerTarball,
149
+ cwd: stage,
150
+ },
151
+ ["bundle.json", "packages"],
152
+ );
153
+ } finally {
154
+ fs.rmSync(stage, { recursive: true, force: true });
155
+ }
156
+
157
+ console.log(`āœ… Bundle tarball: ${outerTarball}`);
158
+ return 0;
159
+ }
160
+
161
+ // Default mode — single per-package tarball
162
+ const workspaceMap = buildWorkspaceMap(args.cwd);
163
+ const tarball = await packPackage({
164
+ pkgDir: args.cwd,
165
+ outDir: args.outDir,
166
+ workspaceMap,
167
+ });
168
+ console.log(`āœ… Tarball: ${tarball}`);
169
+ return 0;
170
+ }
171
+
172
+ function parseArgs(raw: string[]): Args {
173
+ const args: Args = {
174
+ bundle: false,
175
+ outDir: path.resolve("dist"),
176
+ cwd: process.cwd(),
177
+ validateOnly: false,
178
+ };
179
+ for (let i = 0; i < raw.length; i++) {
180
+ const a = raw[i];
181
+ switch (a) {
182
+ case "--bundle": {
183
+ args.bundle = true;
184
+ break;
185
+ }
186
+ case "--validate-only": {
187
+ args.validateOnly = true;
188
+ break;
189
+ }
190
+ case "--out-dir": {
191
+ args.outDir = path.resolve(raw[++i]);
192
+ break;
193
+ }
194
+ case "--cwd": {
195
+ args.cwd = path.resolve(raw[++i]);
196
+ break;
197
+ }
198
+ case "--help":
199
+ case "-h": {
200
+ printHelp();
201
+ process.exit(0);
202
+
203
+ break;
204
+ }
205
+ // No default
206
+ }
207
+ }
208
+ return args;
209
+ }
210
+
211
+ function printHelp(): void {
212
+ console.log(`Usage: checkstack-scripts plugin-pack [options]
213
+
214
+ Packs a Checkstack plugin into a .tgz, validating package.json metadata
215
+ along the way. Run from the directory containing the plugin's package.json.
216
+
217
+ Options:
218
+ --bundle Pack the primary plus every sibling declared in
219
+ package.json#checkstack.bundle into a single outer
220
+ tarball with a bundle.json manifest.
221
+ --out-dir <dir> Output directory (default: ./dist)
222
+ --validate-only Only validate metadata; do not pack.
223
+ --cwd <dir> Run as if invoked from <dir> (default: process.cwd())
224
+ --help, -h Show this message.
225
+ `);
226
+ }
227
+
228
+ function runScriptIfPresent({
229
+ cwd,
230
+ script,
231
+ }: {
232
+ cwd: string;
233
+ script: string;
234
+ }): void {
235
+ const pkg = readJson<{ scripts?: Record<string, string> }>(
236
+ path.join(cwd, "package.json"),
237
+ );
238
+ if (!pkg.scripts?.[script]) return;
239
+ console.log(`ā–¶ bun run ${script}`);
240
+ const r = spawnSync("bun", ["run", script], { cwd, stdio: "inherit" });
241
+ if (r.status !== 0) {
242
+ throw new Error(`bun run ${script} exited with status ${r.status}`);
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Walk up from `cwd` to find the workspace root (`package.json` with a
248
+ * `workspaces` field), then scan every workspace package and build a
249
+ * `name -> dir` map. Returns an empty map when no workspace ancestor
250
+ * exists (standalone plugin repo).
251
+ */
252
+ function buildWorkspaceMap(cwd: string): Map<string, string> {
253
+ const result = new Map<string, string>();
254
+ const root = findWorkspaceRoot(cwd);
255
+ if (!root) return result;
256
+
257
+ const rootPkg = readJson<{ workspaces?: string[] | { packages?: string[] } }>(
258
+ path.join(root, "package.json"),
259
+ );
260
+ const patterns = Array.isArray(rootPkg.workspaces)
261
+ ? rootPkg.workspaces
262
+ : (rootPkg.workspaces?.packages ?? []);
263
+
264
+ for (const pattern of patterns) {
265
+ // Bun/npm globs are typically `core/*` or `plugins/*`. We resolve the
266
+ // direct child layer; deeper nesting is uncommon and not supported here.
267
+ const baseDir = pattern.replace(/\/\*$/, "");
268
+ const absBase = path.join(root, baseDir);
269
+ if (!fs.existsSync(absBase)) continue;
270
+ for (const entry of fs.readdirSync(absBase, { withFileTypes: true })) {
271
+ if (!entry.isDirectory()) continue;
272
+ const dir = path.join(absBase, entry.name);
273
+ const pkgJsonPath = path.join(dir, "package.json");
274
+ if (!fs.existsSync(pkgJsonPath)) continue;
275
+ const pkg = readJson<{ name?: string }>(pkgJsonPath);
276
+ if (pkg.name) result.set(pkg.name, dir);
277
+ }
278
+ }
279
+
280
+ return result;
281
+ }
282
+
283
+ function findWorkspaceRoot(start: string): string | undefined {
284
+ let dir = start;
285
+ while (dir !== path.dirname(dir)) {
286
+ const pkgJsonPath = path.join(dir, "package.json");
287
+ if (fs.existsSync(pkgJsonPath)) {
288
+ const pkg = readJson<{ workspaces?: unknown }>(pkgJsonPath);
289
+ if (pkg.workspaces) return dir;
290
+ }
291
+ dir = path.dirname(dir);
292
+ }
293
+ return undefined;
294
+ }
295
+
296
+ /**
297
+ * Pack a single package into a tarball under `outDir`.
298
+ *
299
+ * Steps:
300
+ * 1. Read package.json
301
+ * 2. Replace `workspace:*` deps with concrete versions from `workspaceMap`
302
+ * 3. Run `bun pm pack` (which produces a tarball using the rewritten
303
+ * package.json)
304
+ * 4. Restore the original package.json on disk so the developer's source
305
+ * tree is unchanged
306
+ */
307
+ async function packPackage({
308
+ pkgDir,
309
+ outDir,
310
+ workspaceMap,
311
+ }: {
312
+ pkgDir: string;
313
+ outDir: string;
314
+ workspaceMap: Map<string, string>;
315
+ }): Promise<string> {
316
+ const pkgJsonPath = path.join(pkgDir, "package.json");
317
+ const original = fs.readFileSync(pkgJsonPath, "utf8");
318
+ const pkg = JSON.parse(original) as InstallPackageMetadata & {
319
+ devDependencies?: Record<string, string>;
320
+ peerDependencies?: Record<string, string>;
321
+ };
322
+
323
+ let rewritten = false;
324
+ for (const section of [
325
+ "dependencies",
326
+ "devDependencies",
327
+ "peerDependencies",
328
+ ] as const) {
329
+ const block = pkg[section];
330
+ if (!block) continue;
331
+ for (const [name, range] of Object.entries(block)) {
332
+ if (range.startsWith("workspace:")) {
333
+ const targetDir = workspaceMap.get(name);
334
+ if (!targetDir) {
335
+ throw new Error(
336
+ `Cannot resolve workspace dep '${name}' (declared in '${pkg.name}'). ` +
337
+ `Either move the package into the workspace or replace the workspace range with a concrete version.`,
338
+ );
339
+ }
340
+ const targetPkg = readJson<{ version: string }>(
341
+ path.join(targetDir, "package.json"),
342
+ );
343
+ block[name] = `^${targetPkg.version}`;
344
+ rewritten = true;
345
+ }
346
+ }
347
+ }
348
+
349
+ try {
350
+ if (rewritten) {
351
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, undefined, 2));
352
+ }
353
+ const r = spawnSync(
354
+ "bun",
355
+ ["pm", "pack", "--destination", outDir],
356
+ { cwd: pkgDir, stdio: "inherit" },
357
+ );
358
+ if (r.status !== 0) {
359
+ throw new Error(`bun pm pack exited with status ${r.status}`);
360
+ }
361
+ } finally {
362
+ if (rewritten) fs.writeFileSync(pkgJsonPath, original);
363
+ }
364
+
365
+ // `bun pm pack` writes `<scope>-<name>-<version>.tgz` (scope-prefix collapsed).
366
+ const expectedName = `${safeFileName(pkg.name)}-${pkg.version}.tgz`;
367
+ const expected = path.join(outDir, expectedName);
368
+ if (!fs.existsSync(expected)) {
369
+ // Fall back: pick the most recently modified tgz in outDir.
370
+ const all = fs
371
+ .readdirSync(outDir)
372
+ .filter((f) => f.endsWith(".tgz"))
373
+ .map((f) => ({
374
+ name: f,
375
+ mtime: fs.statSync(path.join(outDir, f)).mtime.getTime(),
376
+ }))
377
+ .toSorted((a, b) => b.mtime - a.mtime);
378
+ if (all.length === 0) {
379
+ throw new Error(`bun pm pack produced no tarball in ${outDir}`);
380
+ }
381
+ return path.join(outDir, all[0].name);
382
+ }
383
+ return expected;
384
+ }
385
+
386
+ function safeFileName(name: string): string {
387
+ return name.replace(/^@/, "").replaceAll('/', "-");
388
+ }
389
+
390
+ function readJson<T>(p: string): T {
391
+ return JSON.parse(fs.readFileSync(p, "utf8")) as T;
392
+ }
393
+
394
+ if (import.meta.main) {
395
+ const code = await runPluginPack(process.argv.slice(2));
396
+ process.exit(code);
397
+ }
@@ -1,12 +1,41 @@
1
- { "name": "@checkstack/{{pluginName}}", "version": "0.0.1",
2
- "checkstack": { "type": "backend" },
3
- "description": "{{pluginDescription}}", "type": "module", "exports": { ".": {
4
- "import": "./src/index.ts" } }, "scripts": { "typecheck": "tsc --noEmit",
5
- "lint": "bun run lint:code", "lint:code": "eslint . --max-warnings 0", "test":
6
- "bun test" }, "dependencies": { "@checkstack/backend-api": "workspace:*",
7
- "@checkstack/common": "workspace:*", "@checkstack/{{pluginBaseName}}-common":
8
- "workspace:*", "@orpc/server": "^1.13.2", "drizzle-orm": "^0.45.1" },
9
- "devDependencies": { "@checkstack/tsconfig": "workspace:*",
10
- "drizzle-kit": "^0.28.1", "@checkstack/drizzle-helper": "workspace:*",
11
- "@checkstack/test-utils-backend": "workspace:*", "typescript": "^5.7.2" }
12
- }
1
+ {
2
+ "name": "@checkstack/{{pluginName}}",
3
+ "version": "0.0.1",
4
+ "description": "{{pluginDescription}}",
5
+ "author": "Checkstack contributors",
6
+ "license": "Elastic-2.0",
7
+ "type": "module",
8
+ "main": "src/index.ts",
9
+ "exports": {
10
+ ".": { "import": "./src/index.ts" }
11
+ },
12
+ "checkstack": {
13
+ "type": "backend",
14
+ "pluginId": "{{pluginBaseName}}"
15
+ },
16
+ "scripts": {
17
+ "dev": "checkstack-dev",
18
+ "pack": "bunx @checkstack/scripts plugin-pack",
19
+ "typecheck": "tsgo -b",
20
+ "lint": "bun run lint:code",
21
+ "lint:code": "eslint . --max-warnings 0",
22
+ "test": "bun test"
23
+ },
24
+ "dependencies": {
25
+ "@checkstack/backend-api": "workspace:*",
26
+ "@checkstack/common": "workspace:*",
27
+ "@checkstack/{{pluginBaseName}}-common": "workspace:*",
28
+ "@orpc/server": "^1.13.2",
29
+ "drizzle-orm": "^0.45.1"
30
+ },
31
+ "devDependencies": {
32
+ "@checkstack/scripts": "workspace:*",
33
+ "@checkstack/dev-server": "workspace:*",
34
+ "@checkstack/backend": "workspace:*",
35
+ "@checkstack/tsconfig": "workspace:*",
36
+ "@checkstack/drizzle-helper": "workspace:*",
37
+ "@checkstack/test-utils-backend": "workspace:*",
38
+ "drizzle-kit": "^0.28.1",
39
+ "typescript": "^5.7.2"
40
+ }
41
+ }
@@ -1,8 +1,32 @@
1
- { "name": "@checkstack/{{pluginName}}", "version": "0.0.1",
2
- "checkstack": { "type": "common" },
3
- "description": "{{pluginDescription}}", "type": "module", "exports": { ".": {
4
- "import": "./src/index.ts" } }, "scripts": { "typecheck": "tsc --noEmit",
5
- "lint": "bun run lint:code", "lint:code": "eslint . --max-warnings 0" },
6
- "dependencies": { "@checkstack/common": "workspace:*", "@orpc/contract":
7
- "^1.13.2", "zod": "^3.23.0" }, "devDependencies": {
8
- "@checkstack/tsconfig": "workspace:*", "typescript": "^5.7.2" } }
1
+ {
2
+ "name": "@checkstack/{{pluginName}}",
3
+ "version": "0.0.1",
4
+ "description": "{{pluginDescription}}",
5
+ "author": "Checkstack contributors",
6
+ "license": "Elastic-2.0",
7
+ "type": "module",
8
+ "main": "src/index.ts",
9
+ "exports": {
10
+ ".": { "import": "./src/index.ts" }
11
+ },
12
+ "checkstack": {
13
+ "type": "common",
14
+ "pluginId": "{{pluginBaseName}}"
15
+ },
16
+ "scripts": {
17
+ "pack": "bunx @checkstack/scripts plugin-pack",
18
+ "typecheck": "tsgo -b",
19
+ "lint": "bun run lint:code",
20
+ "lint:code": "eslint . --max-warnings 0"
21
+ },
22
+ "dependencies": {
23
+ "@checkstack/common": "workspace:*",
24
+ "@orpc/contract": "^1.13.2",
25
+ "zod": "^3.23.0"
26
+ },
27
+ "devDependencies": {
28
+ "@checkstack/scripts": "workspace:*",
29
+ "@checkstack/tsconfig": "workspace:*",
30
+ "typescript": "^5.7.2"
31
+ }
32
+ }
@@ -1,13 +1,43 @@
1
- { "name": "@checkstack/{{pluginName}}", "version": "0.0.1",
2
- "checkstack": { "type": "frontend" },
3
- "description": "{{pluginDescription}}", "type": "module", "exports": { ".": {
4
- "import": "./src/index.tsx" } }, "scripts": { "typecheck": "tsc --noEmit",
5
- "lint": "bun run lint:code", "lint:code": "eslint . --max-warnings 0",
6
- "test:e2e": "bunx playwright test" }, "dependencies": {
7
- "@checkstack/frontend-api": "workspace:*", "@checkstack/common":
8
- "workspace:*", "@checkstack/{{pluginBaseName}}-common": "workspace:*",
9
- "@checkstack/ui": "workspace:*", "react": "^18.3.1", "react-router-dom":
10
- "^7.1.1", "lucide-react": "^0.469.0" }, "devDependencies": {
11
- "@checkstack/tsconfig": "workspace:*", "@types/react": "^18.3.1",
12
- "@playwright/test": "^1.49.0", "@checkstack/test-utils-frontend":
13
- "workspace:*", "typescript": "^5.7.2" } }
1
+ {
2
+ "name": "@checkstack/{{pluginName}}",
3
+ "version": "0.0.1",
4
+ "description": "{{pluginDescription}}",
5
+ "author": "Checkstack contributors",
6
+ "license": "Elastic-2.0",
7
+ "type": "module",
8
+ "main": "src/index.tsx",
9
+ "exports": {
10
+ ".": { "import": "./src/index.tsx" }
11
+ },
12
+ "checkstack": {
13
+ "type": "frontend",
14
+ "pluginId": "{{pluginBaseName}}"
15
+ },
16
+ "scripts": {
17
+ "dev": "checkstack-dev",
18
+ "pack": "bunx @checkstack/scripts plugin-pack",
19
+ "typecheck": "tsgo -b",
20
+ "lint": "bun run lint:code",
21
+ "lint:code": "eslint . --max-warnings 0",
22
+ "test:e2e": "bunx playwright test"
23
+ },
24
+ "dependencies": {
25
+ "@checkstack/frontend-api": "workspace:*",
26
+ "@checkstack/common": "workspace:*",
27
+ "@checkstack/{{pluginBaseName}}-common": "workspace:*",
28
+ "@checkstack/ui": "workspace:*",
29
+ "react": "^18.3.1",
30
+ "react-router-dom": "^7.1.1",
31
+ "lucide-react": "^0.469.0"
32
+ },
33
+ "devDependencies": {
34
+ "@checkstack/scripts": "workspace:*",
35
+ "@checkstack/dev-server": "workspace:*",
36
+ "@checkstack/frontend": "workspace:*",
37
+ "@checkstack/tsconfig": "workspace:*",
38
+ "@checkstack/test-utils-frontend": "workspace:*",
39
+ "@types/react": "^18.3.1",
40
+ "@playwright/test": "^1.49.0",
41
+ "typescript": "^5.7.2"
42
+ }
43
+ }
@@ -4,8 +4,9 @@ import {
4
4
  prepareTemplateData,
5
5
  registerHelpers,
6
6
  } from "./utils/template";
7
+ import { installPackageMetadataSchema } from "@checkstack/common";
7
8
  import { execSync } from "node:child_process";
8
- import { rmSync, existsSync, mkdirSync } from "node:fs";
9
+ import { rmSync, existsSync, mkdirSync, readFileSync } from "node:fs";
9
10
  import path from "node:path";
10
11
  import { fileURLToPath } from "node:url";
11
12
 
@@ -129,6 +130,70 @@ describe("CLI Template Scaffolding", () => {
129
130
  },
130
131
  { timeout: 30_000 }
131
132
  );
133
+
134
+ // ────────────────────────────────────────────────────────────
135
+ // Dev-server compatibility — one-click `bun run dev` works
136
+ // out of the box. These tests run synchronously against the
137
+ // rendered package.json; no further `bun install` needed
138
+ // because they only inspect static metadata.
139
+ // ────────────────────────────────────────────────────────────
140
+
141
+ it("renders a package.json that the install-time validator accepts", () => {
142
+ // Validate against the same schema dev-server and the runtime
143
+ // plugin installer use. Inlined here (rather than imported from
144
+ // dev-server) so `@checkstack/scripts` doesn't take a dep on
145
+ // `@checkstack/dev-server`.
146
+ const pkgJsonPath = path.join(targetDir, "package.json");
147
+ const raw = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as unknown;
148
+ const result = installPackageMetadataSchema.safeParse(raw);
149
+ if (!result.success) {
150
+ throw new Error(
151
+ `Generated package.json fails install-time validation:\n${result.error.issues
152
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
153
+ .join("\n")}`,
154
+ );
155
+ }
156
+ // Sanity-check the metadata shape so a later schema change
157
+ // surfaces here too.
158
+ expect(result.data.name).toBe(`@checkstack/${pluginName}`);
159
+ expect(result.data.checkstack.type).toBe(pluginType);
160
+ expect(result.data.checkstack.pluginId).toBe(TEST_BASE_NAME);
161
+ expect(result.data.author).toBeDefined();
162
+ expect(result.data.license).toBe("Elastic-2.0");
163
+ });
164
+
165
+ it("includes the @checkstack/scripts devDependency so `bunx @checkstack/scripts plugin-pack` resolves", () => {
166
+ const pkg = JSON.parse(
167
+ readFileSync(path.join(targetDir, "package.json"), "utf8"),
168
+ ) as { devDependencies?: Record<string, string> };
169
+ expect(pkg.devDependencies?.["@checkstack/scripts"]).toBeDefined();
170
+ });
171
+
172
+ it("includes @checkstack/dev-server (backend/frontend only) so the local dev loop is one-click", () => {
173
+ const pkg = JSON.parse(
174
+ readFileSync(path.join(targetDir, "package.json"), "utf8"),
175
+ ) as { devDependencies?: Record<string, string> };
176
+ if (pluginType === "common") {
177
+ // Common packages have no runtime, so the dev server makes no
178
+ // sense for them.
179
+ expect(pkg.devDependencies?.["@checkstack/dev-server"]).toBeUndefined();
180
+ } else {
181
+ expect(pkg.devDependencies?.["@checkstack/dev-server"]).toBeDefined();
182
+ }
183
+ });
184
+
185
+ it("includes the `pack` script (and the `dev` script for backend/frontend)", () => {
186
+ const pkg = JSON.parse(
187
+ readFileSync(path.join(targetDir, "package.json"), "utf8"),
188
+ ) as { scripts?: Record<string, string> };
189
+ expect(pkg.scripts?.pack).toBe("bunx @checkstack/scripts plugin-pack");
190
+ if (pluginType !== "common") {
191
+ // Backend/frontend templates pin the dev script to the
192
+ // `checkstack-dev` bin (provided by `@checkstack/dev-server`,
193
+ // which the template adds as a devDependency).
194
+ expect(pkg.scripts?.dev).toBe("checkstack-dev");
195
+ }
196
+ });
132
197
  });
133
198
  }
134
199
  });
package/CHANGELOG.md DELETED
@@ -1,89 +0,0 @@
1
- # @checkstack/scripts
2
-
3
- ## 0.1.2
4
-
5
- ### Patch Changes
6
-
7
- - 67158e2: Standardize package metadata, unify AJV versions to 8.18.0, and enforce monorepo architecture rules via updated ESLint configuration. This ensures consistent package discovery and runtime dependency safety across the platform.
8
-
9
- ## 0.1.1
10
-
11
- ### Patch Changes
12
-
13
- - 0b9fc58: Fix workspace:\* protocol resolution in published packages
14
-
15
- Published packages now correctly have resolved dependency versions instead of `workspace:*` references. This is achieved by using `bun publish` which properly resolves workspace protocol references.
16
-
17
- ## 0.1.0
18
-
19
- ### Minor Changes
20
-
21
- - 9faec1f: # Unified AccessRule Terminology Refactoring
22
-
23
- This release completes a comprehensive terminology refactoring from "permission" to "accessRule" across the entire codebase, establishing a consistent and modern access control vocabulary.
24
-
25
- ## Changes
26
-
27
- ### Core Infrastructure (`@checkstack/common`)
28
-
29
- - Introduced `AccessRule` interface as the primary access control type
30
- - Added `accessPair()` helper for creating read/manage access rule pairs
31
- - Added `access()` builder for individual access rules
32
- - Replaced `Permission` type with `AccessRule` throughout
33
-
34
- ### API Changes
35
-
36
- - `env.registerPermissions()` → `env.registerAccessRules()`
37
- - `meta.permissions` → `meta.access` in RPC contracts
38
- - `usePermission()` → `useAccess()` in frontend hooks
39
- - Route `permission:` field → `accessRule:` field
40
-
41
- ### UI Changes
42
-
43
- - "Roles & Permissions" tab → "Roles & Access Rules"
44
- - "You don't have permission..." → "You don't have access..."
45
- - All permission-related UI text updated
46
-
47
- ### Documentation & Templates
48
-
49
- - Updated 18 documentation files with AccessRule terminology
50
- - Updated 7 scaffolding templates with `accessPair()` pattern
51
- - All code examples use new AccessRule API
52
-
53
- ## Migration Guide
54
-
55
- ### Backend Plugins
56
-
57
- ```diff
58
- - import { permissionList } from "./permissions";
59
- - env.registerPermissions(permissionList);
60
- + import { accessRules } from "./access";
61
- + env.registerAccessRules(accessRules);
62
- ```
63
-
64
- ### RPC Contracts
65
-
66
- ```diff
67
- - .meta({ userType: "user", permissions: [permissions.read.id] })
68
- + .meta({ userType: "user", access: [access.read] })
69
- ```
70
-
71
- ### Frontend Hooks
72
-
73
- ```diff
74
- - const canRead = accessApi.usePermission(permissions.read.id);
75
- + const canRead = accessApi.useAccess(access.read);
76
- ```
77
-
78
- ### Routes
79
-
80
- ```diff
81
- - permission: permissions.entityRead.id,
82
- + accessRule: access.read,
83
- ```
84
-
85
- ## 0.0.2
86
-
87
- ### Patch Changes
88
-
89
- - d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
package/tsconfig.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "@checkstack/tsconfig/backend.json",
3
- "include": [
4
- "src"
5
- ]
6
- }