@stacksjs/actions 0.70.160 → 0.70.162

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/dist/stacks.js CHANGED
@@ -1,9 +1,19 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
4
+ import process from "node:process";
1
5
  import { log } from "@stacksjs/logging";
2
- import { dirname, join, projectPath, relative, stacksBackupPath, stacksLockPath } from "@stacksjs/path";
6
+ import { registry } from "@stacksjs/registry";
3
7
  import { copyFile, createFolder, deleteFile, deleteEmptyFolders, doesFolderExist, fs } from "@stacksjs/storage";
4
- const STACK_DIRS = ["app", "config", "database", "resources", "routes", "public", "locales"];
5
- async function readStackLock() {
6
- const lockPath = stacksLockPath();
8
+ const STACK_DIRS = ["app", "config", "database", "resources", "routes", "public", "locales", "docs"];
9
+ function stackLockPath(projectRoot) {
10
+ return join(projectRoot, "storage", "framework", "stacks.lock.json");
11
+ }
12
+ function stackBackupPath(projectRoot, stackName) {
13
+ return join(projectRoot, "storage", "framework", "stacks", "backups", stackName);
14
+ }
15
+ async function readStackLock(projectRoot) {
16
+ const lockPath = stackLockPath(projectRoot);
7
17
  try {
8
18
  if (fs.existsSync(lockPath)) {
9
19
  const content = await Bun.file(lockPath).text();
@@ -16,35 +26,47 @@ async function readStackLock() {
16
26
  stacks: {}
17
27
  };
18
28
  }
19
- async function writeStackLock(lock) {
20
- const lockPath = stacksLockPath(), lockDir = dirname(lockPath);
29
+ async function writeStackLock(projectRoot, lock) {
30
+ const lockPath = stackLockPath(projectRoot), lockDir = dirname(lockPath);
21
31
  if (!fs.existsSync(lockDir))
22
32
  await createFolder(lockDir);
23
33
  lock.generatedAt = new Date().toISOString();
24
34
  await Bun.write(lockPath, JSON.stringify(lock, null, 2));
25
35
  }
26
- function resolveStackPackagePath(name) {
27
- const pantryDir = projectPath("pantry"), directPath = join(pantryDir, name);
28
- if (fs.existsSync(join(directPath, "package.json")))
29
- return directPath;
30
- const scopedPath = join(pantryDir, "@stacksjs", name);
31
- if (fs.existsSync(join(scopedPath, "package.json")))
32
- return scopedPath;
33
- if (name.startsWith("@")) {
34
- const fullPath = join(pantryDir, name);
35
- if (fs.existsSync(join(fullPath, "package.json")))
36
- return fullPath;
37
- }
38
- const patterns = ["*/package.json", "@*/*/package.json"];
39
- for (const pattern of patterns) {
40
- const glob = new Bun.Glob(pattern);
41
- for (const file of glob.scanSync({ cwd: pantryDir, absolute: !0, onlyFiles: !0 }))
42
- try {
43
- if (JSON.parse(fs.readFileSync(file, "utf-8"))?.stacks?.name === name)
44
- return dirname(file);
45
- } catch {}
36
+ function resolveProjectRoot(project) {
37
+ return resolve(project || process.cwd());
38
+ }
39
+ function resolveRegistryEntry(name) {
40
+ const entry = registry.find((candidate) => {
41
+ if (typeof candidate === "string")
42
+ return candidate === name;
43
+ return candidate.name === name || candidate.package === name;
44
+ });
45
+ if (!entry || typeof entry === "string")
46
+ return null;
47
+ if (!entry.github)
48
+ return null;
49
+ return { name: entry.name, github: entry.github, package: entry.package };
50
+ }
51
+ async function checkoutRegistryStack(github) {
52
+ const tempRoot = mkdtempSync(join(tmpdir(), "stacks-stack-")), checkoutPath = join(tempRoot, "source"), source = `https://github.com/${github}.git`;
53
+ if (await Bun.spawn(["git", "clone", "--depth", "1", "--quiet", source, checkoutPath], {
54
+ stdout: "inherit",
55
+ stderr: "inherit"
56
+ }).exited !== 0) {
57
+ rmSync(tempRoot, { recursive: !0, force: !0 });
58
+ throw Error(`Could not pull ${source}`);
46
59
  }
47
- return null;
60
+ return {
61
+ path: checkoutPath,
62
+ cleanup: () => rmSync(tempRoot, { recursive: !0, force: !0 })
63
+ };
64
+ }
65
+ function isSafeStackDirectory(directory) {
66
+ return directory.length > 0 && !isAbsolute(directory) && !directory.includes("..") && !directory.includes("\\");
67
+ }
68
+ function fileChecksum(path) {
69
+ return new Bun.CryptoHasher("sha256").update(fs.readFileSync(path)).digest("hex");
48
70
  }
49
71
  function collectFiles(dir, baseDir) {
50
72
  const files = [];
@@ -55,118 +77,151 @@ function collectFiles(dir, baseDir) {
55
77
  const fullPath = join(dir, entry.name);
56
78
  if (entry.isDirectory())
57
79
  files.push(...collectFiles(fullPath, baseDir));
58
- else
59
- files.push(relative(baseDir, fullPath));
80
+ else if (entry.isSymbolicLink())
81
+ throw Error(`Stack contains an unsupported symbolic link: ${relative(baseDir, fullPath)}`);
82
+ else {
83
+ const relPath = relative(baseDir, fullPath);
84
+ if (!isSafeStackDirectory(relPath))
85
+ throw Error(`Stack contains an unsafe path: ${relPath}`);
86
+ files.push(relPath);
87
+ }
60
88
  }
61
89
  return files;
62
90
  }
63
91
  export async function installStack(options) {
64
- const { name, force = !1, dryRun = !1, verbose = !1 } = options, conflict = force ? "overwrite" : options.conflict ?? "skip";
92
+ const { name, force = !1, dryRun = !1, verbose = !1 } = options, conflict = force ? "overwrite" : options.conflict ?? "skip", projectRoot = resolveProjectRoot(options.project);
65
93
  log.info(`Installing stack "${name}"...`);
66
- const stackPath = resolveStackPackagePath(name);
67
- if (!stackPath) {
68
- log.error(`Stack "${name}" not found in pantry. Install it first with: bun add <package-name>`);
94
+ if (!fs.existsSync(join(projectRoot, "package.json"))) {
95
+ log.error(`No Stacks project found at "${projectRoot}"`);
96
+ return null;
97
+ }
98
+ const registryEntry = resolveRegistryEntry(name);
99
+ if (!registryEntry) {
100
+ log.error(`Stack "${name}" is not in the Stacks registry. Run "buddy stack:list" to see available stacks.`);
69
101
  return null;
70
102
  }
71
- let pkg, meta;
103
+ let checkout;
72
104
  try {
73
- const pkgContent = await Bun.file(join(stackPath, "package.json")).text();
74
- pkg = JSON.parse(pkgContent);
75
- meta = pkg.stacks;
76
- if (!meta?.name) {
77
- log.error(`Package at "${stackPath}" does not have a valid "stacks" field in package.json`);
78
- return null;
79
- }
105
+ checkout = await checkoutRegistryStack(registryEntry.github);
80
106
  } catch (error) {
81
- log.error(`Failed to read stack package.json: ${error}`);
107
+ log.error(error instanceof Error ? error.message : String(error));
82
108
  return null;
83
109
  }
84
- const lock = await readStackLock(), packageName = pkg.name || name;
85
- if (lock.stacks[packageName] && !force) {
86
- log.warn(`Stack "${meta.name}" is already installed. Use --force to reinstall.`);
87
- return lock.stacks[packageName];
88
- }
89
- const dirsToScan = meta.directories ?? STACK_DIRS, allFiles = [];
90
- for (const dir of dirsToScan) {
91
- const dirPath = join(stackPath, dir);
92
- if (fs.existsSync(dirPath)) {
93
- const files = collectFiles(dirPath, stackPath);
94
- allFiles.push(...files);
110
+ try {
111
+ const stackPath = checkout.path;
112
+ let pkg, meta;
113
+ try {
114
+ const pkgContent = await Bun.file(join(stackPath, "package.json")).text();
115
+ pkg = JSON.parse(pkgContent);
116
+ meta = pkg.stacks;
117
+ if (!meta?.name || meta.name !== registryEntry.name) {
118
+ log.error(`Registry source "${registryEntry.github}" does not declare stacks.name as "${registryEntry.name}"`);
119
+ return null;
120
+ }
121
+ } catch (error) {
122
+ log.error(`Failed to read stack metadata: ${error}`);
123
+ return null;
95
124
  }
96
- }
97
- if (allFiles.length === 0) {
98
- log.warn(`Stack "${meta.name}" has no files to install`);
99
- return null;
100
- }
101
- if (verbose)
102
- log.info(`Found ${allFiles.length} file(s) to install`);
103
- const copiedFiles = [], skippedFiles = [];
104
- for (const relPath of allFiles) {
105
- const src = join(stackPath, relPath), dest = projectPath(relPath);
106
- if (dryRun) {
107
- if (fs.existsSync(dest)) {
108
- log.info(`[dry-run] Would ${conflict}: ${relPath}`);
109
- if (conflict === "skip")
110
- skippedFiles.push(relPath);
111
- else
112
- copiedFiles.push(relPath);
113
- } else {
114
- log.info(`[dry-run] Would copy: ${relPath}`);
115
- copiedFiles.push(relPath);
125
+ const lock = await readStackLock(projectRoot), packageName = pkg.name || registryEntry.package || name;
126
+ if (lock.stacks[packageName] && !force) {
127
+ log.warn(`Stack "${meta.name}" is already installed. Use --force to reinstall.`);
128
+ return lock.stacks[packageName];
129
+ }
130
+ const dirsToScan = meta.directories ?? STACK_DIRS;
131
+ if (dirsToScan.some((directory) => !isSafeStackDirectory(directory))) {
132
+ log.error(`Stack "${meta.name}" declares an unsafe project directory`);
133
+ return null;
134
+ }
135
+ const allFiles = [];
136
+ try {
137
+ for (const dir of dirsToScan) {
138
+ const dirPath = join(stackPath, dir);
139
+ if (fs.existsSync(dirPath))
140
+ allFiles.push(...collectFiles(dirPath, stackPath));
116
141
  }
117
- continue;
142
+ } catch (error) {
143
+ log.error(error instanceof Error ? error.message : String(error));
144
+ return null;
145
+ }
146
+ if (allFiles.length === 0) {
147
+ log.warn(`Stack "${meta.name}" has no project files to install`);
148
+ return null;
118
149
  }
119
- if (fs.existsSync(dest))
120
- switch (conflict) {
121
- case "skip":
122
- if (verbose)
123
- log.warn(`Skipping (file exists): ${relPath}`);
124
- skippedFiles.push(relPath);
125
- continue;
126
- case "backup": {
127
- const backupDir = stacksBackupPath(meta.name), backupDest = join(backupDir, relPath), backupDestDir = dirname(backupDest);
128
- if (!fs.existsSync(backupDestDir))
129
- await createFolder(backupDestDir);
130
- copyFile(dest, backupDest);
131
- if (verbose)
132
- log.info(`Backed up: ${relPath}`);
133
- break;
150
+ if (verbose)
151
+ log.info(`Pulled ${registryEntry.github} and found ${allFiles.length} project file(s)`);
152
+ const copiedFiles = [], skippedFiles = [], checksums = {};
153
+ for (const relPath of allFiles) {
154
+ const src = join(stackPath, relPath), dest = join(projectRoot, relPath);
155
+ if (dryRun) {
156
+ if (fs.existsSync(dest)) {
157
+ log.info(`[dry-run] Would ${conflict}: ${relPath}`);
158
+ if (conflict === "skip")
159
+ skippedFiles.push(relPath);
160
+ else
161
+ copiedFiles.push(relPath);
162
+ } else {
163
+ log.info(`[dry-run] Would copy: ${relPath}`);
164
+ copiedFiles.push(relPath);
134
165
  }
135
- case "overwrite":
136
- if (verbose)
137
- log.warn(`Overwriting: ${relPath}`);
138
- break;
166
+ continue;
139
167
  }
140
- const destDir = dirname(dest);
141
- if (!fs.existsSync(destDir))
142
- await createFolder(destDir);
143
- copyFile(src, dest);
144
- copiedFiles.push(relPath);
145
- if (verbose)
146
- log.info(`Copied: ${relPath}`);
147
- }
148
- if (dryRun) {
149
- log.info(`[dry-run] Would install ${copiedFiles.length} file(s), skip ${skippedFiles.length} file(s)`);
150
- return null;
168
+ if (fs.existsSync(dest))
169
+ switch (conflict) {
170
+ case "skip":
171
+ if (verbose)
172
+ log.warn(`Skipping (file exists): ${relPath}`);
173
+ skippedFiles.push(relPath);
174
+ continue;
175
+ case "backup": {
176
+ const backupDir = stackBackupPath(projectRoot, meta.name), backupDest = join(backupDir, relPath), backupDestDir = dirname(backupDest);
177
+ if (!fs.existsSync(backupDestDir))
178
+ await createFolder(backupDestDir);
179
+ copyFile(dest, backupDest);
180
+ if (verbose)
181
+ log.info(`Backed up: ${relPath}`);
182
+ break;
183
+ }
184
+ case "overwrite":
185
+ if (verbose)
186
+ log.warn(`Overwriting: ${relPath}`);
187
+ break;
188
+ }
189
+ const destDir = dirname(dest);
190
+ if (!fs.existsSync(destDir))
191
+ await createFolder(destDir);
192
+ copyFile(src, dest);
193
+ copiedFiles.push(relPath);
194
+ checksums[relPath] = fileChecksum(dest);
195
+ if (verbose)
196
+ log.info(`Copied: ${relPath}`);
197
+ }
198
+ const entry = {
199
+ packageName,
200
+ name: meta.name,
201
+ version: pkg.version || "0.0.0",
202
+ installedAt: new Date().toISOString(),
203
+ files: copiedFiles,
204
+ checksums,
205
+ source: registryEntry.github,
206
+ skipped: skippedFiles,
207
+ conflictStrategy: conflict
208
+ };
209
+ if (dryRun) {
210
+ log.info(`[dry-run] Would install ${copiedFiles.length} file(s), skip ${skippedFiles.length} file(s)`);
211
+ return entry;
212
+ }
213
+ lock.stacks[packageName] = entry;
214
+ await writeStackLock(projectRoot, lock);
215
+ log.success(`Installed stack "${meta.name}" from ${registryEntry.github} (${copiedFiles.length} file(s) copied, ${skippedFiles.length} skipped)`);
216
+ return entry;
217
+ } finally {
218
+ checkout.cleanup();
151
219
  }
152
- const entry = {
153
- packageName,
154
- name: meta.name,
155
- version: pkg.version || "0.0.0",
156
- installedAt: new Date().toISOString(),
157
- files: copiedFiles,
158
- skipped: skippedFiles,
159
- conflictStrategy: conflict
160
- };
161
- lock.stacks[packageName] = entry;
162
- await writeStackLock(lock);
163
- log.success(`Installed stack "${meta.name}" (${copiedFiles.length} file(s) copied, ${skippedFiles.length} skipped)`);
164
- return entry;
165
220
  }
166
221
  export async function uninstallStack(options) {
167
- const { name, force = !1, verbose = !1 } = options;
222
+ const { name, force = !1, verbose = !1 } = options, projectRoot = resolveProjectRoot(options.project);
168
223
  log.info(`Uninstalling stack "${name}"...`);
169
- const lock = await readStackLock();
224
+ const lock = await readStackLock(projectRoot);
170
225
  let entryKey = null, entry = null;
171
226
  for (const [key, value] of Object.entries(lock.stacks))
172
227
  if (value.name === name || key === name) {
@@ -179,24 +234,20 @@ export async function uninstallStack(options) {
179
234
  return !1;
180
235
  }
181
236
  let removedCount = 0;
237
+ const remainingFiles = [];
182
238
  for (const relPath of entry.files) {
183
- const filePath = projectPath(relPath);
239
+ const filePath = join(projectRoot, relPath);
184
240
  if (!fs.existsSync(filePath)) {
185
241
  if (verbose)
186
242
  log.info(`Already removed: ${relPath}`);
187
243
  continue;
188
244
  }
189
245
  if (!force) {
190
- const stackPath = resolveStackPackagePath(name);
191
- if (stackPath) {
192
- const srcPath = join(stackPath, relPath);
193
- if (fs.existsSync(srcPath)) {
194
- const srcContent = await Bun.file(srcPath).text(), destContent = await Bun.file(filePath).text();
195
- if (srcContent !== destContent) {
196
- log.warn(`Skipping modified file: ${relPath} (use --force to remove)`);
197
- continue;
198
- }
199
- }
246
+ const installedChecksum = entry.checksums?.[relPath];
247
+ if (!installedChecksum || fileChecksum(filePath) !== installedChecksum) {
248
+ log.warn(`Skipping modified file: ${relPath} (use --force to remove)`);
249
+ remainingFiles.push(relPath);
250
+ continue;
200
251
  }
201
252
  }
202
253
  await deleteFile(filePath);
@@ -204,11 +255,19 @@ export async function uninstallStack(options) {
204
255
  if (verbose)
205
256
  log.info(`Removed: ${relPath}`);
206
257
  }
207
- const backupDir = stacksBackupPath(entry.name);
258
+ if (remainingFiles.length > 0) {
259
+ entry.files = remainingFiles;
260
+ entry.checksums = Object.fromEntries(remainingFiles.map((file) => [file, entry.checksums?.[file]]).filter((item) => typeof item[1] === "string"));
261
+ lock.stacks[entryKey] = entry;
262
+ await writeStackLock(projectRoot, lock);
263
+ log.warn(`Stack "${entry.name}" is still tracked because ${remainingFiles.length} modified file(s) remain`);
264
+ return !1;
265
+ }
266
+ const backupDir = stackBackupPath(projectRoot, entry.name);
208
267
  if (fs.existsSync(backupDir) && doesFolderExist(backupDir)) {
209
268
  const backupFiles = collectFiles(backupDir, backupDir);
210
269
  for (const relPath of backupFiles) {
211
- const backupSrc = join(backupDir, relPath), dest = projectPath(relPath), destDir = dirname(dest);
270
+ const backupSrc = join(backupDir, relPath), dest = join(projectRoot, relPath), destDir = dirname(dest);
212
271
  if (!fs.existsSync(destDir))
213
272
  await createFolder(destDir);
214
273
  copyFile(backupSrc, dest);
@@ -218,38 +277,30 @@ export async function uninstallStack(options) {
218
277
  fs.rmSync(backupDir, { recursive: !0, force: !0 });
219
278
  }
220
279
  for (const dir of STACK_DIRS) {
221
- const dirPath = projectPath(dir);
280
+ const dirPath = join(projectRoot, dir);
222
281
  if (fs.existsSync(dirPath))
223
282
  await deleteEmptyFolders(dirPath);
224
283
  }
225
284
  delete lock.stacks[entryKey];
226
- await writeStackLock(lock);
285
+ await writeStackLock(projectRoot, lock);
227
286
  log.success(`Uninstalled stack "${entry.name}" (${removedCount} file(s) removed)`);
228
287
  return !0;
229
288
  }
230
- export async function listStacks() {
231
- const entries = [], lock = await readStackLock(), pantryDir = projectPath("pantry");
232
- if (fs.existsSync(pantryDir)) {
233
- const patterns = ["*/package.json", "@*/*/package.json"];
234
- for (const pattern of patterns) {
235
- const glob = new Bun.Glob(pattern);
236
- for (const file of glob.scanSync({ cwd: pantryDir, absolute: !0, onlyFiles: !0 }))
237
- try {
238
- const pkgContent = fs.readFileSync(file, "utf-8"), pkg = JSON.parse(pkgContent);
239
- if (!pkg?.stacks?.name)
240
- continue;
241
- const packageName = pkg.name, installed = !!lock.stacks[packageName], lockEntry = lock.stacks[packageName];
242
- entries.push({
243
- name: pkg.stacks.name,
244
- packageName,
245
- version: pkg.version || "0.0.0",
246
- description: pkg.stacks.description,
247
- installed,
248
- installedAt: lockEntry?.installedAt,
249
- fileCount: lockEntry?.files.length
250
- });
251
- } catch {}
252
- }
289
+ export async function listStacks(project) {
290
+ const entries = [], projectRoot = resolveProjectRoot(project), lock = await readStackLock(projectRoot);
291
+ for (const candidate of registry) {
292
+ if (typeof candidate === "string")
293
+ continue;
294
+ const packageName = candidate.package || candidate.name, lockEntry = lock.stacks[packageName];
295
+ entries.push({
296
+ name: candidate.name,
297
+ packageName,
298
+ version: lockEntry?.version || "latest",
299
+ description: candidate.description,
300
+ installed: Boolean(lockEntry),
301
+ installedAt: lockEntry?.installedAt,
302
+ fileCount: lockEntry?.files.length
303
+ });
253
304
  }
254
305
  for (const [packageName, entry] of Object.entries(lock.stacks))
255
306
  if (!entries.some((e) => e.packageName === packageName))
package/dist/templates.js CHANGED
@@ -62,22 +62,25 @@ export default new Action({
62
62
  return { ok: true, userId: user.id, data }
63
63
  },
64
64
  })`,
65
- component: `<script setup lang="ts">
66
- console.log('Hello World component created')
65
+ component: `<script server>
66
+ interface ComponentProps {
67
+ message?: string
68
+ }
69
+
70
+ const { message = 'Hello World component created' } = defineProps<ComponentProps>()
67
71
  </script>
68
72
 
69
73
  <template>
70
- <div>
71
- Some HTML block
72
- </div>
74
+ <div>{{ message }}</div>
73
75
  </template>`,
74
- page: `<script setup lang="ts">
75
- console.log('Hello World page created')
76
+ page: `<script server>
77
+ const heading = 'Hello World page created'
76
78
  </script>
77
79
 
78
80
  <template>
79
81
  <div>
80
- Visit http://127.0.0.1/{0}
82
+ <h1>{{ heading }}</h1>
83
+ <p>Visit /{0}</p>
81
84
  </div>
82
85
  </template>`,
83
86
  function: `// reactive state
@@ -1,11 +1,2 @@
1
- import process from "node:process";
2
- import { log } from "@stacksjs/cli";
3
- import { projectPath } from "@stacksjs/path";
4
- const proc = Bun.spawn(["sh", "-c", "bun test ./tests/feature/**"], {
5
- cwd: projectPath(),
6
- stdio: ["inherit", "inherit", "inherit"]
7
- }), exitCode = await proc.exited;
8
- if (exitCode !== 0) {
9
- log.error("Tests failed");
10
- process.exit(exitCode ?? 1);
11
- }
1
+ import { runTestSuites } from "./runner";
2
+ await runTestSuites(["feature"]);
@@ -1,11 +1,2 @@
1
- import process from "node:process";
2
- import { log } from "@stacksjs/cli";
3
- import { projectPath } from "@stacksjs/path";
4
- const proc = Bun.spawn(["sh", "-c", "bun test --timeout 30000 ./tests/feature/** ./tests/integration/** ./tests/unit/**"], {
5
- cwd: projectPath(),
6
- stdio: ["inherit", "inherit", "inherit"]
7
- }), exitCode = await proc.exited;
8
- if (exitCode !== 0) {
9
- log.error("Tests failed");
10
- process.exit(exitCode ?? 1);
11
- }
1
+ import { runTestSuites } from "./runner";
2
+ await runTestSuites([""], 30000);
@@ -0,0 +1 @@
1
+ export declare function runTestSuites(suites: string[], timeout?: number): Promise<void>;
@@ -0,0 +1,30 @@
1
+ import process from "node:process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { log } from "@stacksjs/cli";
5
+ import { projectPath } from "@stacksjs/path";
6
+ const testFiles = new Bun.Glob("**/*.{test,spec}.{js,jsx,ts,tsx,mjs,mts,cjs,cts}");
7
+ export async function runTestSuites(suites, timeout) {
8
+ const project = projectPath(), filters = suites.flatMap((suite) => {
9
+ const suiteDirectory = join(project, "tests", suite);
10
+ if (!existsSync(suiteDirectory))
11
+ return [];
12
+ return [...testFiles.scanSync({ cwd: suiteDirectory, onlyFiles: !0 })].map((file) => suite ? `./tests/${suite}/${file}` : `./tests/${file}`);
13
+ });
14
+ if (filters.length === 0) {
15
+ log.info("No matching tests found.");
16
+ return;
17
+ }
18
+ const args = ["bun", "test"];
19
+ if (timeout)
20
+ args.push("--timeout", String(timeout));
21
+ args.push(...filters);
22
+ const exitCode = await Bun.spawn(args, {
23
+ cwd: project,
24
+ stdio: ["inherit", "inherit", "inherit"]
25
+ }).exited;
26
+ if (exitCode !== 0) {
27
+ log.error("Tests failed");
28
+ process.exit(exitCode ?? 1);
29
+ }
30
+ }
package/dist/test/unit.js CHANGED
@@ -1,11 +1,2 @@
1
- import process from "node:process";
2
- import { log } from "@stacksjs/cli";
3
- import { projectPath } from "@stacksjs/path";
4
- const proc = Bun.spawn(["sh", "-c", "bun test ./tests/unit/**"], {
5
- cwd: projectPath(),
6
- stdio: ["inherit", "inherit", "inherit"]
7
- }), exitCode = await proc.exited;
8
- if (exitCode !== 0) {
9
- log.error("Tests failed");
10
- process.exit(exitCode ?? 1);
11
- }
1
+ import { runTestSuites } from "./runner";
2
+ await runTestSuites(["unit"]);
@@ -1,3 +1,16 @@
1
+ export declare function hasStacksDependency(pkg: PkgJson): boolean;
2
+ export declare function standalonePackageUpdateCommand(): string;
3
+ /** Strip a range operator (`^`, `~`, `>=`, …) so a declared range compares to a bare version. */
4
+ export declare function baseVersion(range: string): string;
5
+ /*` tooling
6
+ * (e.g. `better-dx`) are excluded, so they are never force-bumped to a framework
7
+ * version they don't publish (stacksjs/stacks#2078).
8
+ */
9
+ export declare function lockstepPackages(metaDeps: Record<string, string>, target: string): Set<string>;
10
+ /*` packages — is left
11
+ * untouched. Returns the set of applied changes.
12
+ */
13
+ export declare function applyBumps(pkg: PkgJson, target: string, lockstep: Set<string>): Array<{ name: string, from: string, to: string }>;
1
14
  /**
2
15
  * Upgrade a node_modules app to a published framework version. Called by the
3
16
  * framework upgrade script when no vendored `storage/framework/core` exists.
@@ -12,3 +25,8 @@ export declare interface PackageUpgradeOptions {
12
25
  dryRun?: boolean
13
26
  noPostinstall?: boolean
14
27
  }
28
+ declare interface PkgJson {
29
+ dependencies?: Record<string, string>
30
+ devDependencies?: Record<string, string>
31
+ [key: string]: unknown
32
+ }