@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 +16 -4
- package/src/cli.ts +20 -5
- package/src/commands/create.ts +17 -0
- package/src/commands/plugin-pack.ts +397 -0
- package/src/templates/backend/package.json.hbs +41 -12
- package/src/templates/common/package.json.hbs +32 -8
- package/src/templates/frontend/package.json.hbs +43 -13
- package/src/templates.test.ts +66 -1
- package/CHANGELOG.md +0 -89
- package/tsconfig.json +0 -6
package/package.json
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/scripts",
|
|
3
|
-
"version": "0.
|
|
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": "
|
|
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.
|
|
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
|
|
11
|
-
console.log(" sync
|
|
12
|
-
console.log(" generate
|
|
13
|
-
console.log("
|
|
14
|
-
console.log("
|
|
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);
|
package/src/commands/create.ts
CHANGED
|
@@ -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
|
-
{
|
|
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": "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
|
-
{
|
|
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,43 @@
|
|
|
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": "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
|
+
}
|
package/src/templates.test.ts
CHANGED
|
@@ -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.
|