@stacksjs/buddy 0.70.258 → 0.70.260

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.
Files changed (112) hide show
  1. package/dist/cli.d.ts +11 -1
  2. package/dist/cli.js +5 -164
  3. package/dist/commands/about.js +1 -35
  4. package/dist/commands/add.js +1 -25
  5. package/dist/commands/ai-context.js +4 -27
  6. package/dist/commands/auth.js +1 -68
  7. package/dist/commands/build.js +1 -221
  8. package/dist/commands/cd.js +8 -39
  9. package/dist/commands/changelog.js +1 -32
  10. package/dist/commands/clean.js +1 -44
  11. package/dist/commands/cloud.js +2 -519
  12. package/dist/commands/commit.js +1 -16
  13. package/dist/commands/completion.js +6 -42
  14. package/dist/commands/config-migrate.js +6 -94
  15. package/dist/commands/configure.js +2 -45
  16. package/dist/commands/create.js +1 -162
  17. package/dist/commands/deploy.js +13 -1676
  18. package/dist/commands/desktop-apple.js +12 -399
  19. package/dist/commands/dev.js +6 -1083
  20. package/dist/commands/dns.js +3 -103
  21. package/dist/commands/docs/buddy-commands.js +10 -132
  22. package/dist/commands/docs/buddy-commands.test.js +1 -38
  23. package/dist/commands/docs/framework-repo.js +1 -14
  24. package/dist/commands/docs/framework-repo.test.js +1 -68
  25. package/dist/commands/docs/generated-artifacts.js +3 -70
  26. package/dist/commands/docs/generated-artifacts.test.js +1 -19
  27. package/dist/commands/docs/links.js +2 -85
  28. package/dist/commands/docs/links.test.js +4 -66
  29. package/dist/commands/docs.js +1 -24
  30. package/dist/commands/doctor.js +2 -478
  31. package/dist/commands/domains.js +2 -152
  32. package/dist/commands/email.js +31 -503
  33. package/dist/commands/env.js +1 -312
  34. package/dist/commands/extension.js +1 -239
  35. package/dist/commands/features.js +9 -345
  36. package/dist/commands/fresh.js +1 -46
  37. package/dist/commands/generate.js +2 -152
  38. package/dist/commands/http.js +1 -33
  39. package/dist/commands/index.js +1 -57
  40. package/dist/commands/install.js +1 -22
  41. package/dist/commands/key.js +1 -23
  42. package/dist/commands/link.js +2 -147
  43. package/dist/commands/lint.js +2 -84
  44. package/dist/commands/list.js +1 -140
  45. package/dist/commands/mail.js +15 -1477
  46. package/dist/commands/maintenance.js +1 -141
  47. package/dist/commands/make.js +1 -392
  48. package/dist/commands/migrate-project.js +1 -49
  49. package/dist/commands/migrate.js +27 -625
  50. package/dist/commands/outdated.js +1 -19
  51. package/dist/commands/package.js +1 -17
  52. package/dist/commands/phone.js +17 -181
  53. package/dist/commands/ports.js +2 -118
  54. package/dist/commands/prepublish.js +1 -17
  55. package/dist/commands/projects.js +1 -29
  56. package/dist/commands/protocol/craft-evidence.js +4 -98
  57. package/dist/commands/protocol/craft-evidence.test.js +1 -20
  58. package/dist/commands/protocol/desktop-lifecycle-report.js +3 -57
  59. package/dist/commands/protocol/desktop-lifecycle-report.test.js +1 -32
  60. package/dist/commands/protocol/desktop-support.js +2 -32
  61. package/dist/commands/protocol/desktop-workflow.test.js +3 -35
  62. package/dist/commands/protocol/driver-registry.js +2 -71
  63. package/dist/commands/protocol/pantry-evidence.js +4 -144
  64. package/dist/commands/protocol/pantry-evidence.test.js +1 -29
  65. package/dist/commands/protocol/release-manifest.js +3 -102
  66. package/dist/commands/protocol/release-manifest.test.js +2 -34
  67. package/dist/commands/protocol/run-conformance.js +4 -346
  68. package/dist/commands/protocol/run-conformance.test.js +1 -52
  69. package/dist/commands/protocol/run-driver-contracts.js +4 -83
  70. package/dist/commands/protocol/run-driver-contracts.test.js +1 -18
  71. package/dist/commands/protocol/source-manifest.js +3 -113
  72. package/dist/commands/protocol/sync-suite.js +3 -136
  73. package/dist/commands/protocol/sync-suite.test.js +1 -44
  74. package/dist/commands/protocol.js +1 -60
  75. package/dist/commands/publish.js +8 -500
  76. package/dist/commands/queue.js +1 -248
  77. package/dist/commands/release.js +1 -42
  78. package/dist/commands/route.js +1 -21
  79. package/dist/commands/run-tool.js +1 -9
  80. package/dist/commands/saas.js +1 -25
  81. package/dist/commands/schedule.js +1 -61
  82. package/dist/commands/search.js +1 -84
  83. package/dist/commands/seed.js +1 -47
  84. package/dist/commands/serve.js +1 -55
  85. package/dist/commands/setup-ai.js +2 -121
  86. package/dist/commands/setup.js +2 -305
  87. package/dist/commands/share.js +1 -214
  88. package/dist/commands/sms.js +32 -319
  89. package/dist/commands/stacks.js +1 -72
  90. package/dist/commands/telemetry.js +1 -74
  91. package/dist/commands/test.js +1 -130
  92. package/dist/commands/tinker.js +1 -37
  93. package/dist/commands/types.js +1 -18
  94. package/dist/commands/upgrade.js +1 -98
  95. package/dist/commands/user.js +3 -84
  96. package/dist/commands/version.js +1 -14
  97. package/dist/config.js +1 -231
  98. package/dist/custom-cli.js +1 -23
  99. package/dist/database-preflight.js +1 -88
  100. package/dist/global-options.js +1 -4
  101. package/dist/index.js +1 -1
  102. package/dist/lazy-commands.js +1 -241
  103. package/dist/migrators/index.js +2 -62
  104. package/dist/migrators/laravel/index.js +1 -148
  105. package/dist/migrators/laravel/migrations.js +4 -231
  106. package/dist/migrators/laravel/models.js +8 -111
  107. package/dist/migrators/rails/index.js +1 -11
  108. package/dist/production-server.js +1 -257
  109. package/dist/project-setup.js +1 -36
  110. package/dist/serve-entry.js +1 -5
  111. package/dist/version-info.js +1 -5
  112. package/package.json +53 -46
@@ -1,500 +1,8 @@
1
- import { existsSync, mkdirSync } from "node:fs";
2
- import { cp, readdir, stat } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
5
- import process from "node:process";
6
- import { italic, log, onUnknownSubcommand } from "@stacksjs/cli";
7
- import { path } from "@stacksjs/path";
8
- import { fs, globSync } from "@stacksjs/storage";
9
- import { ExitCode } from "@stacksjs/types";
10
- export function publish(buddy) {
11
- const descriptions = {
12
- command: "Publish a Stacks default into your userland (app/) directory so you can customize it",
13
- model: "Publish a default model from storage/framework/defaults/app/Models/ to app/Models/",
14
- controller: "Publish a default controller from storage/framework/defaults/app/Controllers/ to app/Controllers/",
15
- middleware: "Publish a default middleware from storage/framework/defaults/app/Middleware/ to app/Middleware/",
16
- action: "Publish a default action from storage/framework/defaults/app/Actions/ to app/Actions/",
17
- core: "Publish a framework package source from node_modules/@stacksjs/<pkg>/ into storage/framework/core/<pkg>/ for editing",
18
- unpublishCore: "Drop a vendored storage/framework/core/<pkg>/ and go back to the installed @stacksjs/<pkg>",
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",
23
- name: "The name of the resource to publish (e.g. Cart, User)",
24
- pkg: "The name of the framework package (e.g. router, orm, faker \u2014 without @stacksjs/ prefix)",
25
- force: "Overwrite an existing userland file",
26
- forceUnpublish: "Delete the vendored source even when it has uncommitted changes",
27
- verbose: "Enable verbose output"
28
- };
29
- buddy.command("publish:model <name>", descriptions.model).option("--force", descriptions.force, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
30
- await publishResource({
31
- kind: "model",
32
- name,
33
- defaultsDir: path.frameworkPath("defaults/app/Models"),
34
- userDir: path.userModelsPath(),
35
- force: !!options.force
36
- });
37
- });
38
- buddy.command("publish:controller <name>", descriptions.controller).option("--force", descriptions.force, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
39
- await publishResource({
40
- kind: "controller",
41
- name,
42
- defaultsDir: path.frameworkPath("defaults/app/Controllers"),
43
- userDir: path.userControllersPath(),
44
- force: !!options.force
45
- });
46
- });
47
- buddy.command("publish:middleware <name>", descriptions.middleware).option("--force", descriptions.force, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
48
- await publishResource({
49
- kind: "middleware",
50
- name,
51
- defaultsDir: path.frameworkPath("defaults/app/Middleware"),
52
- userDir: path.userMiddlewarePath(),
53
- force: !!options.force
54
- });
55
- });
56
- buddy.command("publish:action <name>", descriptions.action).option("--force", descriptions.force, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (name, options) => {
57
- await publishResource({
58
- kind: "action",
59
- name,
60
- defaultsDir: path.frameworkPath("defaults/app/Actions"),
61
- userDir: path.userActionsPath(),
62
- force: !!options.force
63
- });
64
- });
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
- }
74
- await publishCorePackage(pkg, !!options.force);
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
- });
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) => {
80
- if (options.all) {
81
- await unvendorFramework(!!options.force);
82
- return;
83
- }
84
- if (!pkg) {
85
- log.error("Usage: buddy unpublish:core <pkg> (or --all to move the whole framework to node_modules)");
86
- process.exit(ExitCode.FatalError);
87
- }
88
- await unpublishCorePackage(pkg, !!options.force);
89
- });
90
- buddy.command("publish [resource] [name]", descriptions.command).option("--force", descriptions.force, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (resource, name, options) => {
91
- if (!resource || !name) {
92
- log.error("Usage: buddy publish:<resource> <Name> (e.g. buddy publish:model Cart)");
93
- process.exit(ExitCode.FatalError);
94
- }
95
- const handler = {
96
- model: () => publishResource({
97
- kind: "model",
98
- name,
99
- defaultsDir: path.frameworkPath("defaults/app/Models"),
100
- userDir: path.userModelsPath(),
101
- force: !!options.force
102
- }),
103
- controller: () => publishResource({
104
- kind: "controller",
105
- name,
106
- defaultsDir: path.frameworkPath("defaults/app/Controllers"),
107
- userDir: path.userControllersPath(),
108
- force: !!options.force
109
- }),
110
- middleware: () => publishResource({
111
- kind: "middleware",
112
- name,
113
- defaultsDir: path.frameworkPath("defaults/app/Middleware"),
114
- userDir: path.userMiddlewarePath(),
115
- force: !!options.force
116
- }),
117
- action: () => publishResource({
118
- kind: "action",
119
- name,
120
- defaultsDir: path.frameworkPath("defaults/app/Actions"),
121
- userDir: path.userActionsPath(),
122
- force: !!options.force
123
- }),
124
- core: () => publishCorePackage(name, !!options.force)
125
- }[resource.toLowerCase()];
126
- if (!handler) {
127
- log.error(`Unknown publishable resource: ${italic(resource)}`);
128
- log.info("Available: model, controller, middleware, action, core");
129
- process.exit(ExitCode.FatalError);
130
- }
131
- await handler();
132
- });
133
- onUnknownSubcommand(buddy, "publish");
134
- }
135
- async function publishCorePackage(pkg, force) {
136
- const shortName = pkg.replace(/^@stacksjs\//, "").replace(/^core\//, ""), fail = (msg, hint) => {
137
- process.stderr.write(`${msg}
138
- `);
139
- if (hint)
140
- process.stderr.write(` ${hint}
141
- `);
142
- process.exit(ExitCode.FatalError);
143
- };
144
- if (!shortName || shortName.includes("/") || shortName.includes(".."))
145
- fail(`Invalid package name: ${pkg}`, "Use a short name like `router` or the fully qualified `@stacksjs/router`.");
146
- const sourceDir = resolve(process.cwd(), "node_modules", "@stacksjs", shortName), targetDir = path.frameworkPath(`core/${shortName}`);
147
- try {
148
- if (!(await stat(sourceDir)).isDirectory())
149
- fail(`${sourceDir} is not a directory.`);
150
- } catch {
151
- fail(`Could not find @stacksjs/${shortName} in node_modules.`, "Run `bun install` first, or check the package name.");
152
- }
153
- if (existsSync(targetDir) && !force)
154
- fail(`Already published: ${targetDir.replace(`${process.cwd()}/`, "")}`, "Pass --force to overwrite.");
155
- mkdirSync(dirname(targetDir), { recursive: !0 });
156
- const SKIP = new Set(["node_modules", "dist", ".bun", ".cache", "tsconfig.tsbuildinfo"]);
157
- let copied = 0;
158
- await copyTreeFiltered(sourceDir, targetDir, SKIP, () => copied++);
159
- log.success(`Published @stacksjs/${shortName} \u2192 ${italic(targetDir.replace(`${process.cwd()}/`, ""))} (${copied} files)`);
160
- log.info("Edit freely \u2014 local changes win over the installed package.");
161
- }
162
- async function copyTreeFiltered(sourceDir, targetDir, skip, onFile) {
163
- mkdirSync(targetDir, { recursive: !0 });
164
- const entries = await readdir(sourceDir, { withFileTypes: !0 });
165
- for (const entry of entries) {
166
- if (skip.has(entry.name))
167
- continue;
168
- const src = `${sourceDir}/${entry.name}`, dst = `${targetDir}/${entry.name}`;
169
- if (entry.isDirectory()) {
170
- await copyTreeFiltered(src, dst, skip, onFile);
171
- continue;
172
- }
173
- await cp(src, dst, { force: !0, dereference: !0 });
174
- onFile();
175
- }
176
- }
177
- async function publishResource(ctx) {
178
- const { kind, name, defaultsDir, userDir, force } = ctx, fileName = name.endsWith(".ts") ? name : `${name}.ts`, matches = globSync([`${defaultsDir}/**/${fileName}`], { absolute: !0 });
179
- if (!matches.length) {
180
- log.error(`Could not find default ${kind}: ${italic(fileName)}`);
181
- log.info(`Looked under: ${italic(defaultsDir)}`);
182
- process.exit(ExitCode.FatalError);
183
- }
184
- if (matches.length > 1) {
185
- log.warn(`Multiple defaults match ${italic(fileName)}; using the first:`);
186
- for (const m of matches)
187
- log.info(` ${m}`);
188
- }
189
- const sourcePath = matches[0];
190
- if (!sourcePath)
191
- throw Error(`Could not resolve default ${kind}: ${fileName}`);
192
- const targetPath = `${userDir.replace(/\/$/, "")}/${fileName}`;
193
- if (existsSync(targetPath) && !force) {
194
- log.error(`Already exists: ${italic(targetPath)}`);
195
- log.info("Pass --force to overwrite.");
196
- process.exit(ExitCode.FatalError);
197
- }
198
- mkdirSync(dirname(targetPath), { recursive: !0 });
199
- await fs.promises.copyFile(sourcePath, targetPath);
200
- log.success(`Published ${kind} ${italic(name)} \u2192 ${italic(targetPath.replace(`${process.cwd()}/`, ""))}`);
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
- }
332
- async function unpublishCorePackage(pkg, force) {
333
- const shortName = normalizeCoreName(pkg), targetDir = path.frameworkPath(`core/${shortName}`), rel = (p) => p.replace(`${process.cwd()}/`, "");
334
- if (!existsSync(targetDir)) {
335
- log.info(`Not vendored: ${italic(rel(targetDir))} \u2014 nothing to do.`);
336
- return;
337
- }
338
- const installed = resolve(process.cwd(), "node_modules", "@stacksjs", shortName);
339
- if (!existsSync(installed) && !force) {
340
- log.error(`@stacksjs/${shortName} is not installed, so removing the vendored copy would leave nothing to resolve.`);
341
- log.info(`Run \`bun add @stacksjs/${shortName}\` first, or pass --force to remove it anyway.`);
342
- process.exit(ExitCode.FatalError);
343
- }
344
- await assertNoUncommittedChanges(targetDir, force);
345
- await fs.promises.rm(targetDir, { recursive: !0, force: !0 });
346
- log.success(`Unpublished ${italic(rel(targetDir))} \u2014 @stacksjs/${shortName} now resolves from node_modules.`);
347
- }
348
- async function unvendorFramework(force) {
349
- const coreDir = path.frameworkPath("core"), rel = (p) => p.replace(`${process.cwd()}/`, "");
350
- if (!existsSync(coreDir)) {
351
- log.info("No storage/framework/core in this project \u2014 already on the installed packages.");
352
- return;
353
- }
354
- const corePkgPath = resolve(coreDir, "package.json");
355
- if (!existsSync(corePkgPath)) {
356
- log.error(`${rel(coreDir)} has no package.json, so its version cannot be determined.`);
357
- log.info("Unvendor the packages individually with `buddy unpublish:core <pkg>` instead.");
358
- process.exit(ExitCode.FatalError);
359
- }
360
- const corePkg = JSON.parse(await fs.promises.readFile(corePkgPath, "utf-8")), version = corePkg.version;
361
- if (!version) {
362
- log.error(`${rel(corePkgPath)} has no version field.`);
363
- process.exit(ExitCode.FatalError);
364
- }
365
- await assertNoUncommittedChanges(coreDir, force);
366
- const rootPkgPath = resolve(process.cwd(), "package.json"), rootPkgRaw = await fs.promises.readFile(rootPkgPath, "utf-8"), rootPkg = JSON.parse(rootPkgRaw), depName = corePkg.name ?? "stacks", range = `^${version}`, provided = new Set([depName, ...Object.keys(corePkg.dependencies ?? {}).filter((name) => name.startsWith("@stacksjs/"))]);
367
- for (const entry of await readdir(coreDir, { withFileTypes: !0 }))
368
- if (entry.isDirectory())
369
- provided.add(`@stacksjs/${entry.name}`);
370
- let repointed = 0;
371
- const repointWorkspaceRanges = (pkg) => {
372
- let touched = !1;
373
- for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
374
- const deps = pkg[field];
375
- if (!deps)
376
- continue;
377
- for (const [name, spec] of Object.entries(deps)) {
378
- if (!spec.startsWith("workspace:") || !provided.has(name))
379
- continue;
380
- deps[name] = range;
381
- touched = !0;
382
- repointed++;
383
- }
384
- }
385
- return touched;
386
- };
387
- repointWorkspaceRanges(rootPkg);
388
- if (!rootPkg.dependencies?.[depName] && !rootPkg.devDependencies?.[depName])
389
- rootPkg.dependencies = { ...rootPkg.dependencies, [depName]: range };
390
- let rewrittenScripts = 0;
391
- for (const [name, script] of Object.entries(rootPkg.scripts ?? {})) {
392
- if (!/storage\/framework\/core\/buddy\/src\/cli\.ts/.test(script))
393
- continue;
394
- rootPkg.scripts[name] = script.replace(/\bbunx?\s+(?:--bun\s+)?\.?\/?storage\/framework\/core\/buddy\/src\/cli\.ts/g, "./buddy");
395
- rewrittenScripts++;
396
- }
397
- if (Array.isArray(rootPkg.workspaces)) {
398
- rootPkg.workspaces = rootPkg.workspaces.filter((glob) => !isCoreWorkspaceGlob(glob));
399
- if (rootPkg.workspaces.length === 0)
400
- delete rootPkg.workspaces;
401
- }
402
- await fs.promises.writeFile(rootPkgPath, `${JSON.stringify(rootPkg, null, 2)}
403
- `);
404
- for (const glob of rootPkg.workspaces ?? [])
405
- for (const memberPkgPath of globSync(`${glob.replace(/\/$/, "")}/package.json`, { cwd: process.cwd(), absolute: !0 })) {
406
- const raw = await fs.promises.readFile(memberPkgPath, "utf-8"), memberPkg = JSON.parse(raw);
407
- if (repointWorkspaceRanges(memberPkg))
408
- await fs.promises.writeFile(memberPkgPath, `${JSON.stringify(memberPkg, null, 2)}
409
- `);
410
- }
411
- const bunfigPath = resolve(process.cwd(), "bunfig.toml");
412
- let rewrittenPreloads = 0;
413
- if (existsSync(bunfigPath)) {
414
- const bunfig = await fs.promises.readFile(bunfigPath, "utf-8"), next = bunfig.replace(/(["'])\.?\/?storage\/framework\/core\/([\w-]+)\/([^"']+?)\.ts\1/g, (_match, quote, pkgName, subpath) => {
415
- rewrittenPreloads++;
416
- return `${quote}@stacksjs/${pkgName}/${subpath}.js${quote}`;
417
- });
418
- if (next !== bunfig)
419
- await fs.promises.writeFile(bunfigPath, next);
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
- }
430
- await fs.promises.rm(coreDir, { recursive: !0, force: !0 });
431
- const scopedDir = resolve(process.cwd(), "node_modules/@stacksjs");
432
- let danglingRemoved = 0;
433
- if (existsSync(scopedDir))
434
- for (const entry of await readdir(scopedDir, { withFileTypes: !0 })) {
435
- if (!entry.isSymbolicLink())
436
- continue;
437
- const link = resolve(scopedDir, entry.name);
438
- if (existsSync(link))
439
- continue;
440
- await fs.promises.rm(link, { force: !0 });
441
- danglingRemoved++;
442
- }
443
- log.success(`Removed ${italic(rel(coreDir))}`);
444
- log.info(`package.json now depends on ${depName}@${range}`);
445
- if (repointed > 0)
446
- log.info(`Repointed ${repointed} workspace: range${repointed === 1 ? "" : "s"} to ${range}`);
447
- if (danglingRemoved > 0)
448
- log.info(`Removed ${danglingRemoved} node_modules symlink${danglingRemoved === 1 ? "" : "s"} left pointing into it`);
449
- if (rewrittenScripts > 0)
450
- log.info(`Repointed ${rewrittenScripts} package.json script${rewrittenScripts === 1 ? "" : "s"} to ./buddy`);
451
- if (rewrittenPreloads > 0)
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");
455
- log.info("Installing the published packages...");
456
- if (await Bun.spawn(["bun", "install"], { cwd: process.cwd(), stdout: "inherit", stderr: "inherit" }).exited !== 0) {
457
- log.error("`bun install` failed. package.json and bunfig.toml were updated; re-run the install once the failure is resolved.");
458
- process.exit(ExitCode.FatalError);
459
- }
460
- log.success("This project now runs on the published Stacks packages.");
461
- log.info("Vendor an individual package again any time with `buddy publish:core <pkg>`.");
462
- }
463
- function normalizeCoreName(pkg) {
464
- const shortName = pkg.replace(/^@stacksjs\//, "").replace(/^core\//, "");
465
- if (!shortName || shortName.includes("/") || shortName.includes("..")) {
466
- process.stderr.write(`Invalid package name: ${pkg}
467
- `);
468
- process.stderr.write(" Use a short name like `router` or the fully qualified `@stacksjs/router`.\n");
469
- process.exit(ExitCode.FatalError);
470
- }
471
- return shortName;
472
- }
473
- function isCoreWorkspaceGlob(glob) {
474
- const normalized = glob.replace(/^\.\//, "").replace(/\/$/, "");
475
- return normalized === "storage/framework/core" || normalized.startsWith("storage/framework/core/");
476
- }
477
- async function assertNoUncommittedChanges(dir, force) {
478
- if (force)
479
- return;
480
- try {
481
- const proc = Bun.spawn(["git", "status", "--porcelain", "--", dir], {
482
- cwd: process.cwd(),
483
- stdout: "pipe",
484
- stderr: "ignore"
485
- }), output = await new Response(proc.stdout).text();
486
- if (await proc.exited !== 0)
487
- return;
488
- const changed = output.split(`
489
- `).filter(Boolean);
490
- if (changed.length === 0)
491
- return;
492
- log.error(`${changed.length} uncommitted change${changed.length === 1 ? "" : "s"} under ${italic(dir.replace(`${process.cwd()}/`, ""))}:`);
493
- for (const line of changed.slice(0, 10))
494
- log.info(` ${line}`);
495
- if (changed.length > 10)
496
- log.info(` ... and ${changed.length - 10} more`);
497
- log.info("Commit or stash them first, or pass --force to delete them anyway.");
498
- process.exit(ExitCode.FatalError);
499
- } catch {}
500
- }
1
+ import{existsSync,mkdirSync}from"node:fs";import{cp,readdir,stat}from"node:fs/promises";import{homedir}from"node:os";import{dirname,join,resolve}from"node:path";import process from"node:process";import{italic,log,onUnknownSubcommand}from"@stacksjs/cli";import{path}from"@stacksjs/path";import{fs,globSync}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";export function publish(buddy){const descriptions={command:"Publish a Stacks default into your userland (app/) directory so you can customize it",model:"Publish a default model from storage/framework/defaults/app/Models/ to app/Models/",controller:"Publish a default controller from storage/framework/defaults/app/Controllers/ to app/Controllers/",middleware:"Publish a default middleware from storage/framework/defaults/app/Middleware/ to app/Middleware/",action:"Publish a default action from storage/framework/defaults/app/Actions/ to app/Actions/",core:"Publish a framework package source from node_modules/@stacksjs/<pkg>/ into storage/framework/core/<pkg>/ for editing",unpublishCore:"Drop a vendored storage/framework/core/<pkg>/ and go back to the installed @stacksjs/<pkg>",all:"Unvendor the whole framework: remove storage/framework/core and resolve every @stacksjs package from the `stacks` dependency in package.json",publishAll:"Vendor the whole framework: copy storage/framework/core out of a local Stacks checkout and wire it up as a Bun workspace",frameworkPath:"The Stacks checkout to vendor from (defaults to $STACKS_FRAMEWORK_PATH, ../stacks, then ~/Code/stacks)",coreStatus:"Report whether this project runs on a vendored storage/framework/core or on the published packages",name:"The name of the resource to publish (e.g. Cart, User)",pkg:"The name of the framework package (e.g. router, orm, faker \u2014 without @stacksjs/ prefix)",force:"Overwrite an existing userland file",forceUnpublish:"Delete the vendored source even when it has uncommitted changes",verbose:"Enable verbose output"};buddy.command("publish:model <name>",descriptions.model).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(name,options)=>{await publishResource({kind:"model",name,defaultsDir:path.frameworkPath("defaults/app/Models"),userDir:path.userModelsPath(),force:!!options.force})});buddy.command("publish:controller <name>",descriptions.controller).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(name,options)=>{await publishResource({kind:"controller",name,defaultsDir:path.frameworkPath("defaults/app/Controllers"),userDir:path.userControllersPath(),force:!!options.force})});buddy.command("publish:middleware <name>",descriptions.middleware).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(name,options)=>{await publishResource({kind:"middleware",name,defaultsDir:path.frameworkPath("defaults/app/Middleware"),userDir:path.userMiddlewarePath(),force:!!options.force})});buddy.command("publish:action <name>",descriptions.action).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(name,options)=>{await publishResource({kind:"action",name,defaultsDir:path.frameworkPath("defaults/app/Actions"),userDir:path.userActionsPath(),force:!!options.force})});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)=>{if(options.all){await vendorFramework(options.path,!!options.force);return}if(!pkg){log.error("Usage: buddy publish:core <pkg> (or --all to vendor the whole framework as a workspace)");process.exit(ExitCode.FatalError)}await publishCorePackage(pkg,!!options.force)});buddy.command("core:status",descriptions.coreStatus).alias("publish:core:status").option("--verbose",descriptions.verbose,{default:!1}).example("buddy core:status").action(async()=>{await reportCoreStatus()});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)=>{if(options.all){await unvendorFramework(!!options.force);return}if(!pkg){log.error("Usage: buddy unpublish:core <pkg> (or --all to move the whole framework to node_modules)");process.exit(ExitCode.FatalError)}await unpublishCorePackage(pkg,!!options.force)});buddy.command("publish [resource] [name]",descriptions.command).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(resource,name,options)=>{if(!resource||!name){log.error("Usage: buddy publish:<resource> <Name> (e.g. buddy publish:model Cart)");process.exit(ExitCode.FatalError)}const handler={model:()=>publishResource({kind:"model",name,defaultsDir:path.frameworkPath("defaults/app/Models"),userDir:path.userModelsPath(),force:!!options.force}),controller:()=>publishResource({kind:"controller",name,defaultsDir:path.frameworkPath("defaults/app/Controllers"),userDir:path.userControllersPath(),force:!!options.force}),middleware:()=>publishResource({kind:"middleware",name,defaultsDir:path.frameworkPath("defaults/app/Middleware"),userDir:path.userMiddlewarePath(),force:!!options.force}),action:()=>publishResource({kind:"action",name,defaultsDir:path.frameworkPath("defaults/app/Actions"),userDir:path.userActionsPath(),force:!!options.force}),core:()=>publishCorePackage(name,!!options.force)}[resource.toLowerCase()];if(!handler){log.error(`Unknown publishable resource: ${italic(resource)}`);log.info("Available: model, controller, middleware, action, core");process.exit(ExitCode.FatalError)}await handler()});onUnknownSubcommand(buddy,"publish")}async function publishCorePackage(pkg,force){const shortName=pkg.replace(/^@stacksjs\//,"").replace(/^core\//,""),fail=(msg,hint)=>{process.stderr.write(`${msg}
2
+ `);if(hint)process.stderr.write(` ${hint}
3
+ `);process.exit(ExitCode.FatalError)};if(!shortName||shortName.includes("/")||shortName.includes(".."))fail(`Invalid package name: ${pkg}`,"Use a short name like `router` or the fully qualified `@stacksjs/router`.");const sourceDir=resolve(process.cwd(),"node_modules","@stacksjs",shortName),targetDir=path.frameworkPath(`core/${shortName}`);try{if(!(await stat(sourceDir)).isDirectory())fail(`${sourceDir} is not a directory.`)}catch{fail(`Could not find @stacksjs/${shortName} in node_modules.`,"Run `bun install` first, or check the package name.")}if(existsSync(targetDir)&&!force)fail(`Already published: ${targetDir.replace(`${process.cwd()}/`,"")}`,"Pass --force to overwrite.");mkdirSync(dirname(targetDir),{recursive:!0});const SKIP=new Set(["node_modules","dist",".bun",".cache","tsconfig.tsbuildinfo"]);let copied=0;await copyTreeFiltered(sourceDir,targetDir,SKIP,()=>copied++);log.success(`Published @stacksjs/${shortName} \u2192 ${italic(targetDir.replace(`${process.cwd()}/`,""))} (${copied} files)`);log.info("Edit freely \u2014 local changes win over the installed package.")}async function copyTreeFiltered(sourceDir,targetDir,skip,onFile){mkdirSync(targetDir,{recursive:!0});const entries=await readdir(sourceDir,{withFileTypes:!0});for(const entry of entries){if(skip.has(entry.name))continue;const src=`${sourceDir}/${entry.name}`,dst=`${targetDir}/${entry.name}`;if(entry.isDirectory()){await copyTreeFiltered(src,dst,skip,onFile);continue}await cp(src,dst,{force:!0,dereference:!0});onFile()}}async function publishResource(ctx){const{kind,name,defaultsDir,userDir,force}=ctx,fileName=name.endsWith(".ts")?name:`${name}.ts`,matches=globSync([`${defaultsDir}/**/${fileName}`],{absolute:!0});if(!matches.length){log.error(`Could not find default ${kind}: ${italic(fileName)}`);log.info(`Looked under: ${italic(defaultsDir)}`);process.exit(ExitCode.FatalError)}if(matches.length>1){log.warn(`Multiple defaults match ${italic(fileName)}; using the first:`);for(const m of matches)log.info(` ${m}`)}const sourcePath=matches[0];if(!sourcePath)throw Error(`Could not resolve default ${kind}: ${fileName}`);const targetPath=`${userDir.replace(/\/$/,"")}/${fileName}`;if(existsSync(targetPath)&&!force){log.error(`Already exists: ${italic(targetPath)}`);log.info("Pass --force to overwrite.");process.exit(ExitCode.FatalError)}mkdirSync(dirname(targetPath),{recursive:!0});await fs.promises.copyFile(sourcePath,targetPath);log.success(`Published ${kind} ${italic(name)} \u2192 ${italic(targetPath.replace(`${process.cwd()}/`,""))}`)}async function vendorFramework(explicitPath,force){const coreDir=path.frameworkPath("core"),rel=(p)=>p.replace(`${process.cwd()}/`,"");if(existsSync(coreDir)&&!force){log.info(`Already vendored: ${italic(rel(coreDir))}`);log.info("Pass --force to replace it with a fresh copy of the checkout.");return}const framework=resolveFrameworkCheckout(explicitPath);if(!framework){log.error("No Stacks checkout found to vendor from.");log.info("Pass one with `--path <dir>`, or set STACKS_FRAMEWORK_PATH.");log.info("A checkout is required: the published packages ship `dist` only, so a copy of them would not be editable.");process.exit(ExitCode.FatalError)}const sourceCore=join(framework,"storage/framework/core"),corePkgPath=resolve(sourceCore,"package.json");if(!existsSync(corePkgPath)){log.error(`${sourceCore} has no package.json \u2014 that does not look like a Stacks checkout.`);process.exit(ExitCode.FatalError)}const depName=JSON.parse(await fs.promises.readFile(corePkgPath,"utf-8")).name??"stacks";log.info(`Vendoring the framework from ${italic(framework)}...`);if(existsSync(coreDir))await fs.promises.rm(coreDir,{recursive:!0,force:!0});const SKIP=new Set(["node_modules",".bun",".cache","tsconfig.tsbuildinfo"]);let copied=0;await copyTreeFiltered(sourceCore,coreDir,SKIP,()=>copied++);const rootPkgPath=resolve(process.cwd(),"package.json"),rootPkg=JSON.parse(await fs.promises.readFile(rootPkgPath,"utf-8")),provided=new Set([depName]);for(const entry of await readdir(coreDir,{withFileTypes:!0})){if(!entry.isDirectory())continue;if(existsSync(resolve(coreDir,entry.name,"package.json")))provided.add(`@stacksjs/${entry.name}`)}let repointed=0;for(const field of["dependencies","devDependencies"]){const deps=rootPkg[field];if(!deps)continue;for(const name of Object.keys(deps)){if(!provided.has(name)||deps[name].startsWith("workspace:"))continue;deps[name]="workspace:*";repointed++}}if(!rootPkg.dependencies?.[depName]&&!rootPkg.devDependencies?.[depName])rootPkg.dependencies={...rootPkg.dependencies,[depName]:"workspace:*"};const workspaces=rootPkg.workspaces??[];let addedGlobs=0;for(const glob of["storage/framework/core","storage/framework/core/*"]){if(workspaces.some((existing)=>existing.replace(/^\.\//,"").replace(/\/$/,"")===glob))continue;workspaces.push(glob);addedGlobs++}rootPkg.workspaces=workspaces;await fs.promises.writeFile(rootPkgPath,`${JSON.stringify(rootPkg,null,2)}
4
+ `);const bunfigPath=resolve(process.cwd(),"bunfig.toml");let rewrittenPreloads=0;if(existsSync(bunfigPath)){const bunfig=await fs.promises.readFile(bunfigPath,"utf-8"),next=bunfig.replace(/(["'])@stacksjs\/([\w-]+)\/([^"']+?)\.js\1/g,(match,quote,pkgName,subpath)=>{if(!provided.has(`@stacksjs/${pkgName}`))return match;rewrittenPreloads++;return`${quote}./storage/framework/core/${pkgName}/${subpath}.ts${quote}`});if(next!==bunfig)await fs.promises.writeFile(bunfigPath,next)}const tsconfigPath=resolve(process.cwd(),"tsconfig.json");let rewroteTsconfig=!1;if(existsSync(tsconfigPath)){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"');if(next!==raw){await fs.promises.writeFile(tsconfigPath,next);rewroteTsconfig=!0}}log.success(`Vendored ${copied} files into ${italic(rel(coreDir))}`);log.info(`package.json now depends on ${depName}@workspace:*`);if(repointed>0)log.info(`Repointed ${repointed} version range${repointed===1?"":"s"} to workspace:*`);if(addedGlobs>0)log.info(`Added ${addedGlobs} workspace glob${addedGlobs===1?"":"s"} for the vendored packages`);if(rewrittenPreloads>0)log.info(`Rewrote ${rewrittenPreloads} bunfig.toml preload path${rewrittenPreloads===1?"":"s"} to the vendored source`);if(rewroteTsconfig)log.info("tsconfig.json now extends storage/framework/core/tsconfig.json");log.info("Linking the workspace...");if(await Bun.spawn(["bun","install"],{cwd:process.cwd(),stdout:"inherit",stderr:"inherit"}).exited!==0){log.error("`bun install` failed. The files are in place; re-run the install once the failure is resolved.");process.exit(ExitCode.FatalError)}log.success("This project now runs on the vendored framework \u2014 edits under storage/framework/core are live.");log.info("Go back to the published packages any time with `buddy unpublish:core --all`.")}function resolveFrameworkCheckout(explicit){const candidates=[explicit,process.env.STACKS_FRAMEWORK_PATH,resolve(process.cwd(),"../stacks"),join(homedir(),"Code/stacks")].filter(Boolean);for(const candidate of candidates){const full=resolve(candidate);if(full===resolve(process.cwd()))continue;if(existsSync(join(full,"storage/framework/core/package.json")))return full}return null}async function reportCoreStatus(){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;if(vendored){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")));log.info(`Layout: vendored \u2014 ${italic(rel(coreDir))} (${packages.length} packages, v${corePkg.version??"unknown"})`);log.info(`Declared: stacks@${declared??"(not declared)"}`);if(declared&&!declared.startsWith("workspace:"))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.`);log.info("Move to the published packages with `buddy unpublish:core --all`.");return}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;log.info("Layout: published packages \u2014 no storage/framework/core in this project");log.info(`Declared: stacks@${declared??"(not declared)"}`);log.info(`Installed: ${installedVersion?`v${installedVersion}`:"(run bun install)"}`);log.info("Vendor the framework for local development with `buddy publish:core --all`.")}async function unpublishCorePackage(pkg,force){const shortName=normalizeCoreName(pkg),targetDir=path.frameworkPath(`core/${shortName}`),rel=(p)=>p.replace(`${process.cwd()}/`,"");if(!existsSync(targetDir)){log.info(`Not vendored: ${italic(rel(targetDir))} \u2014 nothing to do.`);return}const installed=resolve(process.cwd(),"node_modules","@stacksjs",shortName);if(!existsSync(installed)&&!force){log.error(`@stacksjs/${shortName} is not installed, so removing the vendored copy would leave nothing to resolve.`);log.info(`Run \`bun add @stacksjs/${shortName}\` first, or pass --force to remove it anyway.`);process.exit(ExitCode.FatalError)}await assertNoUncommittedChanges(targetDir,force);await fs.promises.rm(targetDir,{recursive:!0,force:!0});log.success(`Unpublished ${italic(rel(targetDir))} \u2014 @stacksjs/${shortName} now resolves from node_modules.`)}async function unvendorFramework(force){const coreDir=path.frameworkPath("core"),rel=(p)=>p.replace(`${process.cwd()}/`,"");if(!existsSync(coreDir)){log.info("No storage/framework/core in this project \u2014 already on the installed packages.");return}const corePkgPath=resolve(coreDir,"package.json");if(!existsSync(corePkgPath)){log.error(`${rel(coreDir)} has no package.json, so its version cannot be determined.`);log.info("Unvendor the packages individually with `buddy unpublish:core <pkg>` instead.");process.exit(ExitCode.FatalError)}const corePkg=JSON.parse(await fs.promises.readFile(corePkgPath,"utf-8")),version=corePkg.version;if(!version){log.error(`${rel(corePkgPath)} has no version field.`);process.exit(ExitCode.FatalError)}await assertNoUncommittedChanges(coreDir,force);const rootPkgPath=resolve(process.cwd(),"package.json"),rootPkgRaw=await fs.promises.readFile(rootPkgPath,"utf-8"),rootPkg=JSON.parse(rootPkgRaw),depName=corePkg.name??"stacks",range=`^${version}`,provided=new Set([depName,...Object.keys(corePkg.dependencies??{}).filter((name)=>name.startsWith("@stacksjs/"))]);for(const entry of await readdir(coreDir,{withFileTypes:!0}))if(entry.isDirectory())provided.add(`@stacksjs/${entry.name}`);let repointed=0;const repointWorkspaceRanges=(pkg)=>{let touched=!1;for(const field of["dependencies","devDependencies","peerDependencies"]){const deps=pkg[field];if(!deps)continue;for(const[name,spec]of Object.entries(deps)){if(!spec.startsWith("workspace:")||!provided.has(name))continue;deps[name]=range;touched=!0;repointed++}}return touched};repointWorkspaceRanges(rootPkg);if(!rootPkg.dependencies?.[depName]&&!rootPkg.devDependencies?.[depName])rootPkg.dependencies={...rootPkg.dependencies,[depName]:range};let rewrittenScripts=0;for(const[name,script]of Object.entries(rootPkg.scripts??{})){if(!/storage\/framework\/core\/buddy\/src\/cli\.ts/.test(script))continue;rootPkg.scripts[name]=script.replace(/\bbunx?\s+(?:--bun\s+)?\.?\/?storage\/framework\/core\/buddy\/src\/cli\.ts/g,"./buddy");rewrittenScripts++}if(Array.isArray(rootPkg.workspaces)){rootPkg.workspaces=rootPkg.workspaces.filter((glob)=>!isCoreWorkspaceGlob(glob));if(rootPkg.workspaces.length===0)delete rootPkg.workspaces}await fs.promises.writeFile(rootPkgPath,`${JSON.stringify(rootPkg,null,2)}
5
+ `);for(const glob of rootPkg.workspaces??[])for(const memberPkgPath of globSync(`${glob.replace(/\/$/,"")}/package.json`,{cwd:process.cwd(),absolute:!0})){const raw=await fs.promises.readFile(memberPkgPath,"utf-8"),memberPkg=JSON.parse(raw);if(repointWorkspaceRanges(memberPkg))await fs.promises.writeFile(memberPkgPath,`${JSON.stringify(memberPkg,null,2)}
6
+ `)}const bunfigPath=resolve(process.cwd(),"bunfig.toml");let rewrittenPreloads=0;if(existsSync(bunfigPath)){const bunfig=await fs.promises.readFile(bunfigPath,"utf-8"),next=bunfig.replace(/(["'])\.?\/?storage\/framework\/core\/([\w-]+)\/([^"']+?)\.ts\1/g,(_match,quote,pkgName,subpath)=>{rewrittenPreloads++;return`${quote}@stacksjs/${pkgName}/${subpath}.js${quote}`});if(next!==bunfig)await fs.promises.writeFile(bunfigPath,next)}const tsconfigPath=resolve(process.cwd(),"tsconfig.json");let rewroteTsconfig=!1;if(existsSync(tsconfigPath)){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"');if(next!==raw){await fs.promises.writeFile(tsconfigPath,next);rewroteTsconfig=!0}}await fs.promises.rm(coreDir,{recursive:!0,force:!0});const scopedDir=resolve(process.cwd(),"node_modules/@stacksjs");let danglingRemoved=0;if(existsSync(scopedDir))for(const entry of await readdir(scopedDir,{withFileTypes:!0})){if(!entry.isSymbolicLink())continue;const link=resolve(scopedDir,entry.name);if(existsSync(link))continue;await fs.promises.rm(link,{force:!0});danglingRemoved++}log.success(`Removed ${italic(rel(coreDir))}`);log.info(`package.json now depends on ${depName}@${range}`);if(repointed>0)log.info(`Repointed ${repointed} workspace: range${repointed===1?"":"s"} to ${range}`);if(danglingRemoved>0)log.info(`Removed ${danglingRemoved} node_modules symlink${danglingRemoved===1?"":"s"} left pointing into it`);if(rewrittenScripts>0)log.info(`Repointed ${rewrittenScripts} package.json script${rewrittenScripts===1?"":"s"} to ./buddy`);if(rewrittenPreloads>0)log.info(`Rewrote ${rewrittenPreloads} bunfig.toml preload path${rewrittenPreloads===1?"":"s"} to package specifiers`);if(rewroteTsconfig)log.info("tsconfig.json now extends storage/framework/tsconfig.app.json");log.info("Installing the published packages...");if(await Bun.spawn(["bun","install"],{cwd:process.cwd(),stdout:"inherit",stderr:"inherit"}).exited!==0){log.error("`bun install` failed. package.json and bunfig.toml were updated; re-run the install once the failure is resolved.");process.exit(ExitCode.FatalError)}log.success("This project now runs on the published Stacks packages.");log.info("Vendor an individual package again any time with `buddy publish:core <pkg>`.")}function normalizeCoreName(pkg){const shortName=pkg.replace(/^@stacksjs\//,"").replace(/^core\//,"");if(!shortName||shortName.includes("/")||shortName.includes("..")){process.stderr.write(`Invalid package name: ${pkg}
7
+ `);process.stderr.write(" Use a short name like `router` or the fully qualified `@stacksjs/router`.\n");process.exit(ExitCode.FatalError)}return shortName}function isCoreWorkspaceGlob(glob){const normalized=glob.replace(/^\.\//,"").replace(/\/$/,"");return normalized==="storage/framework/core"||normalized.startsWith("storage/framework/core/")}async function assertNoUncommittedChanges(dir,force){if(force)return;try{const proc=Bun.spawn(["git","status","--porcelain","--",dir],{cwd:process.cwd(),stdout:"pipe",stderr:"ignore"}),output=await new Response(proc.stdout).text();if(await proc.exited!==0)return;const changed=output.split(`
8
+ `).filter(Boolean);if(changed.length===0)return;log.error(`${changed.length} uncommitted change${changed.length===1?"":"s"} under ${italic(dir.replace(`${process.cwd()}/`,""))}:`);for(const line of changed.slice(0,10))log.info(` ${line}`);if(changed.length>10)log.info(` ... and ${changed.length-10} more`);log.info("Commit or stash them first, or pass --force to delete them anyway.");process.exit(ExitCode.FatalError)}catch{}}