@checkstack/scripts 0.1.2 → 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 +22 -4
- package/src/cli.ts +31 -5
- package/src/commands/create.ts +17 -0
- package/src/commands/dev-deps-resolver.test.ts +411 -0
- package/src/commands/dev-deps-resolver.ts +215 -0
- package/src/commands/dev-frontend.test.ts +148 -0
- package/src/commands/dev-frontend.ts +198 -0
- package/src/commands/dev-internals.test.ts +506 -0
- package/src/commands/dev-internals.ts +278 -0
- package/src/commands/dev-lifecycle.test.ts +327 -0
- package/src/commands/dev-lifecycle.ts +173 -0
- package/src/commands/dev-server.ts +197 -0
- package/src/commands/plugin-pack.ts +397 -0
- package/src/templates/backend/package.json.hbs +39 -12
- package/src/templates/common/package.json.hbs +32 -8
- package/src/templates/frontend/package.json.hbs +41 -13
- package/src/templates.test.ts +48 -1
- package/CHANGELOG.md +0 -89
- package/tsconfig.json +0 -6
|
@@ -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,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
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": "bunx @checkstack/scripts 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/tsconfig": "workspace:*",
|
|
34
|
+
"@checkstack/drizzle-helper": "workspace:*",
|
|
35
|
+
"@checkstack/test-utils-backend": "workspace:*",
|
|
36
|
+
"drizzle-kit": "^0.28.1",
|
|
37
|
+
"typescript": "^5.7.2"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,8 +1,32 @@
|
|
|
1
|
-
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
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,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
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": "bunx @checkstack/scripts 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/tsconfig": "workspace:*",
|
|
36
|
+
"@checkstack/test-utils-frontend": "workspace:*",
|
|
37
|
+
"@types/react": "^18.3.1",
|
|
38
|
+
"@playwright/test": "^1.49.0",
|
|
39
|
+
"typescript": "^5.7.2"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/templates.test.ts
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
prepareTemplateData,
|
|
5
5
|
registerHelpers,
|
|
6
6
|
} from "./utils/template";
|
|
7
|
+
import { validatePluginPackageJson } from "./commands/dev-internals";
|
|
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,52 @@ 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 dev-server's validator accepts", () => {
|
|
142
|
+
const result = validatePluginPackageJson({ cwd: targetDir });
|
|
143
|
+
if (!result.ok) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Generated package.json fails dev-server validation:\n${
|
|
146
|
+
"issues" in result
|
|
147
|
+
? result.issues.join("\n")
|
|
148
|
+
: "package.json missing"
|
|
149
|
+
}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
// Sanity-check the metadata shape so a later schema change
|
|
153
|
+
// surfaces here too.
|
|
154
|
+
expect(result.metadata.name).toBe(`@checkstack/${pluginName}`);
|
|
155
|
+
expect(result.metadata.checkstack.type).toBe(pluginType);
|
|
156
|
+
expect(result.metadata.checkstack.pluginId).toBe(TEST_BASE_NAME);
|
|
157
|
+
expect(result.metadata.author).toBeDefined();
|
|
158
|
+
expect(result.metadata.license).toBe("Elastic-2.0");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("includes the @checkstack/scripts devDependency so bunx resolves the dev/pack commands", () => {
|
|
162
|
+
const pkg = JSON.parse(
|
|
163
|
+
readFileSync(path.join(targetDir, "package.json"), "utf8"),
|
|
164
|
+
) as { devDependencies?: Record<string, string> };
|
|
165
|
+
expect(pkg.devDependencies?.["@checkstack/scripts"]).toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("includes the `pack` script (and the `dev` script for backend/frontend)", () => {
|
|
169
|
+
const pkg = JSON.parse(
|
|
170
|
+
readFileSync(path.join(targetDir, "package.json"), "utf8"),
|
|
171
|
+
) as { scripts?: Record<string, string> };
|
|
172
|
+
expect(pkg.scripts?.pack).toBe("bunx @checkstack/scripts plugin-pack");
|
|
173
|
+
if (pluginType !== "common") {
|
|
174
|
+
// Common packages have no runtime, so a dev server makes no
|
|
175
|
+
// sense for them — but backend/frontend must be one-click.
|
|
176
|
+
expect(pkg.scripts?.dev).toBe("bunx @checkstack/scripts dev");
|
|
177
|
+
}
|
|
178
|
+
});
|
|
132
179
|
});
|
|
133
180
|
}
|
|
134
181
|
});
|