@stacksjs/buddy 0.70.248 → 0.70.250

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.
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync } from "node:fs";
2
2
  import { cp, readdir, stat } from "node:fs/promises";
3
- import { dirname, resolve } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
4
5
  import process from "node:process";
5
6
  import { italic, log, onUnknownSubcommand } from "@stacksjs/cli";
6
7
  import { path } from "@stacksjs/path";
@@ -16,6 +17,9 @@ export function publish(buddy) {
16
17
  core: "Publish a framework package source from node_modules/@stacksjs/<pkg>/ into storage/framework/core/<pkg>/ for editing",
17
18
  unpublishCore: "Drop a vendored storage/framework/core/<pkg>/ and go back to the installed @stacksjs/<pkg>",
18
19
  all: "Unvendor the whole framework: remove storage/framework/core and resolve every @stacksjs package from the `stacks` dependency in package.json",
20
+ publishAll: "Vendor the whole framework: copy storage/framework/core out of a local Stacks checkout and wire it up as a Bun workspace",
21
+ frameworkPath: "The Stacks checkout to vendor from (defaults to $STACKS_FRAMEWORK_PATH, ../stacks, then ~/Code/stacks)",
22
+ coreStatus: "Report whether this project runs on a vendored storage/framework/core or on the published packages",
19
23
  name: "The name of the resource to publish (e.g. Cart, User)",
20
24
  pkg: "The name of the framework package (e.g. router, orm, faker \u2014 without @stacksjs/ prefix)",
21
25
  force: "Overwrite an existing userland file",
@@ -58,9 +62,20 @@ export function publish(buddy) {
58
62
  force: !!options.force
59
63
  });
60
64
  });
61
- buddy.command("publish:core <pkg>", descriptions.core).option("--force", descriptions.force, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (pkg, options) => {
65
+ buddy.command("publish:core [pkg]", descriptions.core).option("--all", descriptions.publishAll, { default: !1 }).option("--path <path>", descriptions.frameworkPath).option("--force", descriptions.force, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).example("buddy publish:core router").example("buddy publish:core --all").example("buddy publish:core --all --path ../stacks").action(async (pkg, options) => {
66
+ if (options.all) {
67
+ await vendorFramework(options.path, !!options.force);
68
+ return;
69
+ }
70
+ if (!pkg) {
71
+ log.error("Usage: buddy publish:core <pkg> (or --all to vendor the whole framework as a workspace)");
72
+ process.exit(ExitCode.FatalError);
73
+ }
62
74
  await publishCorePackage(pkg, !!options.force);
63
75
  });
76
+ buddy.command("core:status", descriptions.coreStatus).alias("publish:core:status").option("--verbose", descriptions.verbose, { default: !1 }).example("buddy core:status").action(async () => {
77
+ await reportCoreStatus();
78
+ });
64
79
  buddy.command("unpublish:core [pkg]", descriptions.unpublishCore).option("--all", descriptions.all, { default: !1 }).option("--force", descriptions.forceUnpublish, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (pkg, options) => {
65
80
  if (options.all) {
66
81
  await unvendorFramework(!!options.force);
@@ -184,6 +199,136 @@ async function publishResource(ctx) {
184
199
  await fs.promises.copyFile(sourcePath, targetPath);
185
200
  log.success(`Published ${kind} ${italic(name)} \u2192 ${italic(targetPath.replace(`${process.cwd()}/`, ""))}`);
186
201
  }
202
+ async function vendorFramework(explicitPath, force) {
203
+ const coreDir = path.frameworkPath("core"), rel = (p) => p.replace(`${process.cwd()}/`, "");
204
+ if (existsSync(coreDir) && !force) {
205
+ log.info(`Already vendored: ${italic(rel(coreDir))}`);
206
+ log.info("Pass --force to replace it with a fresh copy of the checkout.");
207
+ return;
208
+ }
209
+ const framework = resolveFrameworkCheckout(explicitPath);
210
+ if (!framework) {
211
+ log.error("No Stacks checkout found to vendor from.");
212
+ log.info("Pass one with `--path <dir>`, or set STACKS_FRAMEWORK_PATH.");
213
+ log.info("A checkout is required: the published packages ship `dist` only, so a copy of them would not be editable.");
214
+ process.exit(ExitCode.FatalError);
215
+ }
216
+ const sourceCore = join(framework, "storage/framework/core"), corePkgPath = resolve(sourceCore, "package.json");
217
+ if (!existsSync(corePkgPath)) {
218
+ log.error(`${sourceCore} has no package.json \u2014 that does not look like a Stacks checkout.`);
219
+ process.exit(ExitCode.FatalError);
220
+ }
221
+ const depName = JSON.parse(await fs.promises.readFile(corePkgPath, "utf-8")).name ?? "stacks";
222
+ log.info(`Vendoring the framework from ${italic(framework)}...`);
223
+ if (existsSync(coreDir))
224
+ await fs.promises.rm(coreDir, { recursive: !0, force: !0 });
225
+ const SKIP = new Set(["node_modules", ".bun", ".cache", "tsconfig.tsbuildinfo"]);
226
+ let copied = 0;
227
+ await copyTreeFiltered(sourceCore, coreDir, SKIP, () => copied++);
228
+ const rootPkgPath = resolve(process.cwd(), "package.json"), rootPkg = JSON.parse(await fs.promises.readFile(rootPkgPath, "utf-8")), provided = new Set([depName]);
229
+ for (const entry of await readdir(coreDir, { withFileTypes: !0 })) {
230
+ if (!entry.isDirectory())
231
+ continue;
232
+ if (existsSync(resolve(coreDir, entry.name, "package.json")))
233
+ provided.add(`@stacksjs/${entry.name}`);
234
+ }
235
+ let repointed = 0;
236
+ for (const field of ["dependencies", "devDependencies"]) {
237
+ const deps = rootPkg[field];
238
+ if (!deps)
239
+ continue;
240
+ for (const name of Object.keys(deps)) {
241
+ if (!provided.has(name) || deps[name].startsWith("workspace:"))
242
+ continue;
243
+ deps[name] = "workspace:*";
244
+ repointed++;
245
+ }
246
+ }
247
+ if (!rootPkg.dependencies?.[depName] && !rootPkg.devDependencies?.[depName])
248
+ rootPkg.dependencies = { ...rootPkg.dependencies, [depName]: "workspace:*" };
249
+ const workspaces = rootPkg.workspaces ?? [];
250
+ let addedGlobs = 0;
251
+ for (const glob of ["storage/framework/core", "storage/framework/core/*"]) {
252
+ if (workspaces.some((existing) => existing.replace(/^\.\//, "").replace(/\/$/, "") === glob))
253
+ continue;
254
+ workspaces.push(glob);
255
+ addedGlobs++;
256
+ }
257
+ rootPkg.workspaces = workspaces;
258
+ await fs.promises.writeFile(rootPkgPath, `${JSON.stringify(rootPkg, null, 2)}
259
+ `);
260
+ const bunfigPath = resolve(process.cwd(), "bunfig.toml");
261
+ let rewrittenPreloads = 0;
262
+ if (existsSync(bunfigPath)) {
263
+ const bunfig = await fs.promises.readFile(bunfigPath, "utf-8"), next = bunfig.replace(/(["'])@stacksjs\/([\w-]+)\/([^"']+?)\.js\1/g, (match, quote, pkgName, subpath) => {
264
+ if (!provided.has(`@stacksjs/${pkgName}`))
265
+ return match;
266
+ rewrittenPreloads++;
267
+ return `${quote}./storage/framework/core/${pkgName}/${subpath}.ts${quote}`;
268
+ });
269
+ if (next !== bunfig)
270
+ await fs.promises.writeFile(bunfigPath, next);
271
+ }
272
+ const tsconfigPath = resolve(process.cwd(), "tsconfig.json");
273
+ let rewroteTsconfig = !1;
274
+ if (existsSync(tsconfigPath)) {
275
+ const raw = await fs.promises.readFile(tsconfigPath, "utf-8"), next = raw.replace(/"extends"\s*:\s*"\.\/storage\/framework\/tsconfig\.app\.json"/, '"extends": "./storage/framework/core/tsconfig.json"');
276
+ if (next !== raw) {
277
+ await fs.promises.writeFile(tsconfigPath, next);
278
+ rewroteTsconfig = !0;
279
+ }
280
+ }
281
+ log.success(`Vendored ${copied} files into ${italic(rel(coreDir))}`);
282
+ log.info(`package.json now depends on ${depName}@workspace:*`);
283
+ if (repointed > 0)
284
+ log.info(`Repointed ${repointed} version range${repointed === 1 ? "" : "s"} to workspace:*`);
285
+ if (addedGlobs > 0)
286
+ log.info(`Added ${addedGlobs} workspace glob${addedGlobs === 1 ? "" : "s"} for the vendored packages`);
287
+ if (rewrittenPreloads > 0)
288
+ log.info(`Rewrote ${rewrittenPreloads} bunfig.toml preload path${rewrittenPreloads === 1 ? "" : "s"} to the vendored source`);
289
+ if (rewroteTsconfig)
290
+ log.info("tsconfig.json now extends storage/framework/core/tsconfig.json");
291
+ log.info("Linking the workspace...");
292
+ if (await Bun.spawn(["bun", "install"], { cwd: process.cwd(), stdout: "inherit", stderr: "inherit" }).exited !== 0) {
293
+ log.error("`bun install` failed. The files are in place; re-run the install once the failure is resolved.");
294
+ process.exit(ExitCode.FatalError);
295
+ }
296
+ log.success("This project now runs on the vendored framework \u2014 edits under storage/framework/core are live.");
297
+ log.info("Go back to the published packages any time with `buddy unpublish:core --all`.");
298
+ }
299
+ function resolveFrameworkCheckout(explicit) {
300
+ const candidates = [
301
+ explicit,
302
+ process.env.STACKS_FRAMEWORK_PATH,
303
+ resolve(process.cwd(), "../stacks"),
304
+ join(homedir(), "Code/stacks")
305
+ ].filter(Boolean);
306
+ for (const candidate of candidates) {
307
+ const full = resolve(candidate);
308
+ if (full === resolve(process.cwd()))
309
+ continue;
310
+ if (existsSync(join(full, "storage/framework/core/package.json")))
311
+ return full;
312
+ }
313
+ return null;
314
+ }
315
+ async function reportCoreStatus() {
316
+ const coreDir = path.frameworkPath("core"), rel = (p) => p.replace(`${process.cwd()}/`, ""), vendored = existsSync(resolve(coreDir, "package.json")), rootPkgPath = resolve(process.cwd(), "package.json"), rootPkg = existsSync(rootPkgPath) ? JSON.parse(await fs.promises.readFile(rootPkgPath, "utf-8")) : {}, declared = rootPkg.dependencies?.stacks ?? rootPkg.devDependencies?.stacks;
317
+ if (vendored) {
318
+ const corePkg = JSON.parse(await fs.promises.readFile(resolve(coreDir, "package.json"), "utf-8")), packages = (await readdir(coreDir, { withFileTypes: !0 })).filter((entry) => entry.isDirectory() && existsSync(resolve(coreDir, entry.name, "package.json")));
319
+ log.info(`Layout: vendored \u2014 ${italic(rel(coreDir))} (${packages.length} packages, v${corePkg.version ?? "unknown"})`);
320
+ log.info(`Declared: stacks@${declared ?? "(not declared)"}`);
321
+ if (declared && !declared.startsWith("workspace:"))
322
+ log.warn(`The vendored copy is not linked: stacks is declared as ${declared}, not workspace:*. Run \`buddy publish:core --all --force\` to relink, or \`buddy unpublish:core --all\` to remove it.`);
323
+ log.info("Move to the published packages with `buddy unpublish:core --all`.");
324
+ return;
325
+ }
326
+ const installed = resolve(process.cwd(), "node_modules/@stacksjs/buddy/package.json"), installedVersion = existsSync(installed) ? JSON.parse(await fs.promises.readFile(installed, "utf-8")).version : void 0;
327
+ log.info("Layout: published packages \u2014 no storage/framework/core in this project");
328
+ log.info(`Declared: stacks@${declared ?? "(not declared)"}`);
329
+ log.info(`Installed: ${installedVersion ? `v${installedVersion}` : "(run bun install)"}`);
330
+ log.info("Vendor the framework for local development with `buddy publish:core --all`.");
331
+ }
187
332
  async function unpublishCorePackage(pkg, force) {
188
333
  const shortName = normalizeCoreName(pkg), targetDir = path.frameworkPath(`core/${shortName}`), rel = (p) => p.replace(`${process.cwd()}/`, "");
189
334
  if (!existsSync(targetDir)) {
@@ -273,6 +418,15 @@ async function unvendorFramework(force) {
273
418
  if (next !== bunfig)
274
419
  await fs.promises.writeFile(bunfigPath, next);
275
420
  }
421
+ const tsconfigPath = resolve(process.cwd(), "tsconfig.json");
422
+ let rewroteTsconfig = !1;
423
+ if (existsSync(tsconfigPath)) {
424
+ const raw = await fs.promises.readFile(tsconfigPath, "utf-8"), next = raw.replace(/"extends"\s*:\s*"\.\/storage\/framework\/core\/tsconfig\.json"/, '"extends": "./storage/framework/tsconfig.app.json"');
425
+ if (next !== raw) {
426
+ await fs.promises.writeFile(tsconfigPath, next);
427
+ rewroteTsconfig = !0;
428
+ }
429
+ }
276
430
  await fs.promises.rm(coreDir, { recursive: !0, force: !0 });
277
431
  const scopedDir = resolve(process.cwd(), "node_modules/@stacksjs");
278
432
  let danglingRemoved = 0;
@@ -296,6 +450,8 @@ async function unvendorFramework(force) {
296
450
  log.info(`Repointed ${rewrittenScripts} package.json script${rewrittenScripts === 1 ? "" : "s"} to ./buddy`);
297
451
  if (rewrittenPreloads > 0)
298
452
  log.info(`Rewrote ${rewrittenPreloads} bunfig.toml preload path${rewrittenPreloads === 1 ? "" : "s"} to package specifiers`);
453
+ if (rewroteTsconfig)
454
+ log.info("tsconfig.json now extends storage/framework/tsconfig.app.json");
299
455
  log.info("Installing the published packages...");
300
456
  if (await Bun.spawn(["bun", "install"], { cwd: process.cwd(), stdout: "inherit", stderr: "inherit" }).exited !== 0) {
301
457
  log.error("`bun install` failed. package.json and bunfig.toml were updated; re-run the install once the failure is resolved.");
@@ -95,6 +95,7 @@ const commandRegistry = {
95
95
  "publish:middleware": { path: "./commands/publish.js", exportName: "publish" },
96
96
  "publish:action": { path: "./commands/publish.js", exportName: "publish" },
97
97
  "publish:core": { path: "./commands/publish.js", exportName: "publish" },
98
+ "core:status": { path: "./commands/publish.js", exportName: "publish" },
98
99
  queue: { path: "./commands/queue.js", exportName: "queue" },
99
100
  release: { path: "./commands/release.js", exportName: "release" },
100
101
  route: { path: "./commands/route.js", exportName: "route" },
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/buddy",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.248",
5
+ "version": "0.70.250",
6
6
  "description": "Meet Buddy. The Stacks runtime.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -88,53 +88,53 @@
88
88
  "prepublishOnly": "bun run build"
89
89
  },
90
90
  "dependencies": {
91
- "@stacksjs/actions": "^0.70.248",
92
- "@stacksjs/ai": "^0.70.248",
93
- "@stacksjs/alias": "^0.70.248",
94
- "@stacksjs/arrays": "^0.70.248",
95
- "@stacksjs/auth": "^0.70.248",
96
- "@stacksjs/build": "^0.70.248",
97
- "@stacksjs/cache": "^0.70.248",
98
- "@stacksjs/cli": "^0.70.248",
91
+ "@stacksjs/actions": "^0.70.250",
92
+ "@stacksjs/ai": "^0.70.250",
93
+ "@stacksjs/alias": "^0.70.250",
94
+ "@stacksjs/arrays": "^0.70.250",
95
+ "@stacksjs/auth": "^0.70.250",
96
+ "@stacksjs/build": "^0.70.250",
97
+ "@stacksjs/cache": "^0.70.250",
98
+ "@stacksjs/cli": "^0.70.250",
99
99
  "@stacksjs/clapp": "^0.2.12",
100
- "@stacksjs/cloud": "^0.70.248",
101
- "@stacksjs/collections": "^0.70.248",
102
- "@stacksjs/config": "^0.70.248",
103
- "@stacksjs/database": "^0.70.248",
104
- "@stacksjs/desktop-build": "^0.70.248",
105
- "@stacksjs/dns": "^0.70.248",
106
- "@stacksjs/email": "^0.70.248",
107
- "@stacksjs/enums": "^0.70.248",
108
- "@stacksjs/error-handling": "^0.70.248",
109
- "@stacksjs/events": "^0.70.248",
110
- "@stacksjs/git": "^0.70.248",
100
+ "@stacksjs/cloud": "^0.70.250",
101
+ "@stacksjs/collections": "^0.70.250",
102
+ "@stacksjs/config": "^0.70.250",
103
+ "@stacksjs/database": "^0.70.250",
104
+ "@stacksjs/desktop-build": "^0.70.250",
105
+ "@stacksjs/dns": "^0.70.250",
106
+ "@stacksjs/email": "^0.70.250",
107
+ "@stacksjs/enums": "^0.70.250",
108
+ "@stacksjs/error-handling": "^0.70.250",
109
+ "@stacksjs/events": "^0.70.250",
110
+ "@stacksjs/git": "^0.70.250",
111
111
  "@stacksjs/gitit": "^0.2.5",
112
- "@stacksjs/health": "^0.70.248",
112
+ "@stacksjs/health": "^0.70.250",
113
113
  "@stacksjs/dnsx": "^0.2.3",
114
114
  "@stacksjs/httx": "^0.1.10",
115
- "@stacksjs/image": "^0.70.248",
116
- "@stacksjs/lint": "^0.70.248",
117
- "@stacksjs/logging": "^0.70.248",
118
- "@stacksjs/notifications": "^0.70.248",
119
- "@stacksjs/objects": "^0.70.248",
120
- "@stacksjs/orm": "^0.70.248",
121
- "@stacksjs/path": "^0.70.248",
122
- "@stacksjs/skills": "^0.70.248",
123
- "@stacksjs/payments": "^0.70.248",
124
- "@stacksjs/realtime": "^0.70.248",
125
- "@stacksjs/router": "^0.70.248",
115
+ "@stacksjs/image": "^0.70.250",
116
+ "@stacksjs/lint": "^0.70.250",
117
+ "@stacksjs/logging": "^0.70.250",
118
+ "@stacksjs/notifications": "^0.70.250",
119
+ "@stacksjs/objects": "^0.70.250",
120
+ "@stacksjs/orm": "^0.70.250",
121
+ "@stacksjs/path": "^0.70.250",
122
+ "@stacksjs/skills": "^0.70.250",
123
+ "@stacksjs/payments": "^0.70.250",
124
+ "@stacksjs/realtime": "^0.70.250",
125
+ "@stacksjs/router": "^0.70.250",
126
126
  "@stacksjs/rpx": "^0.11.42",
127
- "@stacksjs/search-engine": "^0.70.248",
128
- "@stacksjs/security": "^0.70.248",
129
- "@stacksjs/server": "^0.70.248",
130
- "@stacksjs/storage": "^0.70.248",
131
- "@stacksjs/strings": "^0.70.248",
132
- "@stacksjs/testing": "^0.70.248",
133
- "@stacksjs/tunnel": "^0.70.248",
134
- "@stacksjs/types": "^0.70.248",
135
- "@stacksjs/ui": "^0.70.248",
136
- "@stacksjs/utils": "^0.70.248",
137
- "@stacksjs/validation": "^0.70.248",
127
+ "@stacksjs/search-engine": "^0.70.250",
128
+ "@stacksjs/security": "^0.70.250",
129
+ "@stacksjs/server": "^0.70.250",
130
+ "@stacksjs/storage": "^0.70.250",
131
+ "@stacksjs/strings": "^0.70.250",
132
+ "@stacksjs/testing": "^0.70.250",
133
+ "@stacksjs/tunnel": "^0.70.250",
134
+ "@stacksjs/types": "^0.70.250",
135
+ "@stacksjs/ui": "^0.70.250",
136
+ "@stacksjs/utils": "^0.70.250",
137
+ "@stacksjs/validation": "^0.70.250",
138
138
  "@stacksjs/ts-cloud": "^0.7.92",
139
139
  "ajv": "^8.20.0",
140
140
  "ajv-formats": "^3.0.1",