@simplysm/sd-cli 13.0.95 → 13.0.97

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 (41) hide show
  1. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  2. package/dist/orchestrators/DevOrchestrator.js +6 -4
  3. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  4. package/dist/orchestrators/WatchOrchestrator.d.ts +7 -0
  5. package/dist/orchestrators/WatchOrchestrator.d.ts.map +1 -1
  6. package/dist/orchestrators/WatchOrchestrator.js +93 -49
  7. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  8. package/dist/sd-config.types.d.ts +14 -1
  9. package/dist/sd-config.types.d.ts.map +1 -1
  10. package/dist/utils/package-utils.js +1 -1
  11. package/dist/utils/package-utils.js.map +1 -1
  12. package/dist/utils/vite-config.d.ts +4 -0
  13. package/dist/utils/vite-config.d.ts.map +1 -1
  14. package/dist/utils/vite-config.js +4 -1
  15. package/dist/utils/vite-config.js.map +1 -1
  16. package/dist/workers/client.worker.d.ts.map +1 -1
  17. package/dist/workers/client.worker.js +7 -2
  18. package/dist/workers/client.worker.js.map +1 -1
  19. package/dist/workers/server-runtime.worker.d.ts +1 -0
  20. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  21. package/dist/workers/server-runtime.worker.js +5 -0
  22. package/dist/workers/server-runtime.worker.js.map +1 -1
  23. package/package.json +6 -6
  24. package/src/orchestrators/DevOrchestrator.ts +4 -1
  25. package/src/orchestrators/WatchOrchestrator.ts +124 -67
  26. package/src/sd-config.types.ts +15 -1
  27. package/src/utils/package-utils.ts +2 -2
  28. package/src/utils/vite-config.ts +9 -1
  29. package/src/workers/client.worker.ts +9 -1
  30. package/src/workers/server-runtime.worker.ts +8 -0
  31. package/templates/init/.gitignore.hbs +1 -2
  32. package/templates/init/package.json.hbs +5 -6
  33. package/templates/init/packages/client-admin/package.json.hbs +7 -7
  34. package/templates/init/packages/db-main/package.json.hbs +2 -2
  35. package/templates/init/packages/server/package.json.hbs +5 -5
  36. package/templates/init/tests-e2e/package.json.hbs +1 -1
  37. package/tests/run-watch.spec.ts +35 -0
  38. package/tests/vite-config-outdir.spec.ts +38 -0
  39. package/README.md +0 -337
  40. package/docs/architecture.md +0 -311
  41. package/docs/config-types.md +0 -253
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/workers/server-runtime.worker.ts"],
4
- "mappings": "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",
4
+ "mappings": "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",
5
5
  "names": []
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/sd-cli",
3
- "version": "13.0.95",
3
+ "version": "13.0.97",
4
4
  "description": "Simplysm package - CLI tool",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -21,8 +21,8 @@
21
21
  ],
22
22
  "sideEffects": false,
23
23
  "dependencies": {
24
- "@fastify/http-proxy": "^11.4.1",
25
- "@inquirer/prompts": "^8.3.0",
24
+ "@fastify/http-proxy": "^11.4.2",
25
+ "@inquirer/prompts": "^8.3.2",
26
26
  "consola": "^3.4.2",
27
27
  "esbuild": "^0.27.4",
28
28
  "esbuild-plugin-solid": "^0.6.0",
@@ -43,9 +43,9 @@
43
43
  "vite-plugin-solid": "^2.11.11",
44
44
  "vite-tsconfig-paths": "^6.1.1",
45
45
  "yargs": "^18.0.0",
46
- "@simplysm/core-common": "13.0.95",
47
- "@simplysm/core-node": "13.0.95",
48
- "@simplysm/storage": "13.0.95"
46
+ "@simplysm/core-node": "13.0.97",
47
+ "@simplysm/core-common": "13.0.97",
48
+ "@simplysm/storage": "13.0.97"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/semver": "^7.7.1",
@@ -498,7 +498,7 @@ export class DevOrchestrator {
498
498
  };
499
499
 
500
500
  // Register Server Build Worker event handlers
501
- for (const { name } of this._serverPackages) {
501
+ for (const { name, config } of this._serverPackages) {
502
502
  const serverBuild = this._serverBuildWorkers.get(name)!;
503
503
 
504
504
  serverBuild.worker.on("buildStart", () => {
@@ -540,6 +540,7 @@ export class DevOrchestrator {
540
540
  serverRuntimePromises,
541
541
  resolveServerStep,
542
542
  viteClientReadyPromises,
543
+ { ...this._baseEnv, ...config.env },
543
544
  ).catch((err: unknown) => {
544
545
  const message = errNs.message(err);
545
546
  this._logger.error(`[${name}] Error starting Server Runtime:`, message);
@@ -610,6 +611,7 @@ export class DevOrchestrator {
610
611
  serverRuntimePromises: Map<string, { promise: Promise<void>; resolver: () => void }>,
611
612
  resolveServerStep: (serverName: string, resultKey: string, result: BuildResult) => void,
612
613
  viteClientReadyPromises: Map<string, { promise: Promise<void>; resolver: () => void }>,
614
+ env?: Record<string, string>,
613
615
  ): Promise<void> {
614
616
  const runtimeStartTime = performance.now();
615
617
  this._logger.debug(`[${serverName}] _startServerRuntime: ${mainJsPath}`);
@@ -692,6 +694,7 @@ export class DevOrchestrator {
692
694
  .start({
693
695
  mainJsPath,
694
696
  clientPorts: serverClientPorts,
697
+ env,
695
698
  })
696
699
  .catch((err: unknown) => {
697
700
  const message = errNs.message(err);
@@ -1,6 +1,7 @@
1
1
  import path from "path";
2
+ import { spawn } from "child_process";
2
3
  import { consola } from "consola";
3
- import type { BuildTarget, SdBuildPackageConfig, SdConfig } from "../sd-config.types";
4
+ import type { BuildTarget, SdBuildPackageConfig, SdConfig, SdScriptsPackageConfig } from "../sd-config.types";
4
5
  import { loadSdConfig } from "../utils/sd-config";
5
6
  import { filterPackagesByTargets } from "../utils/package-utils";
6
7
  import { watchReplaceDeps, type WatchReplaceDepResult } from "../utils/replace-deps";
@@ -12,7 +13,7 @@ import { LibraryBuilder } from "../builders/LibraryBuilder";
12
13
  import { DtsBuilder } from "../builders/DtsBuilder";
13
14
  import type { BuildPackageInfo } from "../builders/types";
14
15
  import { watchCopySrcFiles } from "../utils/copy-src";
15
- import type { FsWatcher } from "@simplysm/core-node";
16
+ import { FsWatcher } from "@simplysm/core-node";
16
17
 
17
18
  /**
18
19
  * Watch command options
@@ -42,6 +43,8 @@ export class WatchOrchestrator {
42
43
  private _packages: BuildPackageInfo[] = [];
43
44
  private _copySrcWatchers: FsWatcher[] = [];
44
45
  private _replaceDepWatcher: WatchReplaceDepResult | undefined;
46
+ private readonly _watchHookPackages: Array<{ name: string; dir: string; config: SdScriptsPackageConfig }> = [];
47
+ private readonly _watchHookWatchers: FsWatcher[] = [];
45
48
 
46
49
  constructor(options: WatchOrchestratorOptions) {
47
50
  this._cwd = process.cwd();
@@ -80,7 +83,7 @@ export class WatchOrchestrator {
80
83
  // Filter by targets
81
84
  const allPackages = filterPackagesByTargets(sdConfig.packages, this._options.targets);
82
85
 
83
- // Filter only library packages (node, browser, neutral)
86
+ // Classify packages: library (node/browser/neutral) vs scripts+watch
84
87
  const isLibraryTarget = (target: string): target is BuildTarget =>
85
88
  target === "node" || target === "browser" || target === "neutral";
86
89
 
@@ -88,15 +91,21 @@ export class WatchOrchestrator {
88
91
  for (const [name, config] of Object.entries(allPackages)) {
89
92
  if (isLibraryTarget(config.target)) {
90
93
  libraryConfigs[name] = config as SdBuildPackageConfig;
94
+ } else if (config.target === "scripts" && config.watch != null) {
95
+ this._watchHookPackages.push({
96
+ name,
97
+ dir: path.join(this._cwd, "packages", name),
98
+ config: config,
99
+ });
91
100
  }
92
101
  }
93
102
 
94
- if (Object.keys(libraryConfigs).length === 0) {
95
- process.stdout.write("⚠ No library packages to watch.\n");
103
+ if (Object.keys(libraryConfigs).length === 0 && this._watchHookPackages.length === 0) {
104
+ process.stdout.write("⚠ No packages to watch.\n");
96
105
  return;
97
106
  }
98
107
 
99
- // Create PackageInfo array
108
+ // Create PackageInfo array for library packages
100
109
  this._packages = Object.entries(libraryConfigs).map(([name, config]) => ({
101
110
  name,
102
111
  dir: path.join(this._cwd, "packages", name),
@@ -104,94 +113,135 @@ export class WatchOrchestrator {
104
113
  }));
105
114
 
106
115
  // Initialize infrastructure
107
- this._resultCollector = new ResultCollector();
108
116
  this._signalHandler = new SignalHandler();
109
- this._rebuildManager = new RebuildManager(this._logger);
110
117
 
111
- // Print errors on batch completion
112
- this._rebuildManager.on("batchComplete", () => {
113
- printErrors(this._resultCollector.toMap());
114
- });
118
+ // Initialize library builders only if there are library packages
119
+ if (this._packages.length > 0) {
120
+ this._resultCollector = new ResultCollector();
121
+ this._rebuildManager = new RebuildManager(this._logger);
122
+
123
+ // Print errors on batch completion
124
+ this._rebuildManager.on("batchComplete", () => {
125
+ printErrors(this._resultCollector.toMap());
126
+ });
115
127
 
116
- // Create builders
117
- const builderOptions = {
118
- cwd: this._cwd,
119
- packages: this._packages,
120
- resultCollector: this._resultCollector,
121
- rebuildManager: this._rebuildManager,
122
- };
128
+ // Create builders
129
+ const builderOptions = {
130
+ cwd: this._cwd,
131
+ packages: this._packages,
132
+ resultCollector: this._resultCollector,
133
+ rebuildManager: this._rebuildManager,
134
+ };
123
135
 
124
- this._libraryBuilder = new LibraryBuilder(builderOptions);
125
- this._dtsBuilder = new DtsBuilder(builderOptions);
136
+ this._libraryBuilder = new LibraryBuilder(builderOptions);
137
+ this._dtsBuilder = new DtsBuilder(builderOptions);
126
138
 
127
- // Initialize builders
128
- await Promise.all([this._libraryBuilder.initialize(), this._dtsBuilder.initialize()]);
139
+ // Initialize builders
140
+ await Promise.all([this._libraryBuilder.initialize(), this._dtsBuilder.initialize()]);
141
+ }
129
142
  }
130
143
 
131
144
  /**
132
145
  * Start watch mode
133
146
  * - Run initial build
147
+ * - Start watch hook watchers
134
148
  * - Output results
135
149
  */
136
150
  async start(): Promise<void> {
137
- if (this._packages.length === 0) {
151
+ if (this._packages.length === 0 && this._watchHookPackages.length === 0) {
138
152
  return;
139
153
  }
140
154
 
141
- // Set up initial build promises
142
- const buildPromises = this._libraryBuilder.getInitialBuildPromises();
143
- const dtsPromises = this._dtsBuilder.getInitialBuildPromises();
155
+ // Start library build if there are library packages
156
+ if (this._packages.length > 0) {
157
+ // Set up initial build promises
158
+ const buildPromises = this._libraryBuilder.getInitialBuildPromises();
159
+ const dtsPromises = this._dtsBuilder.getInitialBuildPromises();
160
+
161
+ // Start copySrc watch
162
+ for (const pkg of this._packages) {
163
+ if (pkg.config.copySrc != null && pkg.config.copySrc.length > 0) {
164
+ const watcher = await watchCopySrcFiles(pkg.dir, pkg.config.copySrc);
165
+ this._copySrcWatchers.push(watcher);
166
+ }
167
+ }
168
+
169
+ // Start watch (run in background)
170
+ void this._libraryBuilder.startWatch();
171
+ void this._dtsBuilder.startWatch();
144
172
 
145
- // Start copySrc watch
146
- for (const pkg of this._packages) {
147
- if (pkg.config.copySrc != null && pkg.config.copySrc.length > 0) {
148
- const watcher = await watchCopySrcFiles(pkg.dir, pkg.config.copySrc);
149
- this._copySrcWatchers.push(watcher);
173
+ // Start initial build
174
+ this._logger.start("Running initial build...");
175
+
176
+ // Set up complete promise array for library build and DTS build
177
+ const allBuildTasks: Array<{ name: string; promise: Promise<void> }> = [];
178
+
179
+ // Library build tasks
180
+ for (const pkg of this._packages) {
181
+ const promise = buildPromises.get(`${pkg.name}:build`) ?? Promise.resolve();
182
+ allBuildTasks.push({
183
+ name: `${pkg.name}:build`,
184
+ promise,
185
+ });
150
186
  }
151
- }
152
187
 
153
- // Start watch (run in background)
154
- void this._libraryBuilder.startWatch();
155
- void this._dtsBuilder.startWatch();
188
+ // DTS tasks
189
+ for (const pkg of this._packages) {
190
+ const promise = dtsPromises.get(`${pkg.name}:dts`) ?? Promise.resolve();
191
+ allBuildTasks.push({
192
+ name: `${pkg.name}:dts`,
193
+ promise,
194
+ });
195
+ }
156
196
 
157
- // Start initial build
158
- this._logger.start("Running initial build...");
197
+ // Run all build tasks concurrently (wait until initial build completes)
198
+ await Promise.allSettled(allBuildTasks.map((task) => task.promise));
159
199
 
160
- // Set up complete promise array for library build and DTS build
161
- const allBuildTasks: Array<{ name: string; promise: Promise<void> }> = [];
200
+ this._logger.success("Initial build completed");
162
201
 
163
- // Library build tasks
164
- for (const pkg of this._packages) {
165
- const promise = buildPromises.get(`${pkg.name}:build`) ?? Promise.resolve();
166
- allBuildTasks.push({
167
- name: `${pkg.name}:build`,
168
- promise,
169
- });
202
+ // Output initial build results
203
+ printErrors(this._resultCollector.toMap());
170
204
  }
171
205
 
172
- // DTS tasks
173
- for (const pkg of this._packages) {
174
- const promise = dtsPromises.get(`${pkg.name}:dts`) ?? Promise.resolve();
175
- allBuildTasks.push({
176
- name: `${pkg.name}:dts`,
177
- promise,
178
- });
179
- }
206
+ // Start watch hook watchers for scripts+watch packages
207
+ for (const pkg of this._watchHookPackages) {
208
+ const watchConfig = pkg.config.watch!;
209
+ const watchTargets = watchConfig.target.map((t) => path.resolve(pkg.dir, t));
180
210
 
181
- // Run all build tasks concurrently (wait until initial build completes)
182
- await Promise.allSettled(allBuildTasks.map((task) => task.promise));
211
+ // Run initial hook
212
+ this._runWatchHookCmd(pkg.name, pkg.dir, watchConfig.cmd, watchConfig.args);
183
213
 
184
- this._logger.success("Initial build completed");
214
+ // Start watching
215
+ const watcher = await FsWatcher.watch(watchTargets);
216
+ watcher.onChange({ delay: 300 }, () => {
217
+ this._runWatchHookCmd(pkg.name, pkg.dir, watchConfig.cmd, watchConfig.args);
218
+ });
219
+ this._watchHookWatchers.push(watcher);
185
220
 
186
- // Output initial build results
187
- printErrors(this._resultCollector.toMap());
221
+ this._logger.success(`Watch hook started: ${pkg.name}`);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Run watch hook command
227
+ */
228
+ private _runWatchHookCmd(pkgName: string, cwd: string, cmd: string, args?: string[]): void {
229
+ const child = spawn(cmd, args ?? [], { cwd, stdio: "inherit", shell: true });
230
+ child.on("error", (err) => {
231
+ this._logger.error(`Watch hook error (${pkgName}): ${err.message}`);
232
+ });
233
+ child.on("close", (code) => {
234
+ if (code !== 0 && code !== null) {
235
+ this._logger.warn(`Watch hook (${pkgName}) exited with code ${String(code)}`);
236
+ }
237
+ });
188
238
  }
189
239
 
190
240
  /**
191
241
  * Wait for termination signal
192
242
  */
193
243
  async awaitTermination(): Promise<void> {
194
- if (this._packages.length === 0) {
244
+ if (this._packages.length === 0 && this._watchHookPackages.length === 0) {
195
245
  return;
196
246
  }
197
247
  await this._signalHandler.waitForTermination();
@@ -201,18 +251,25 @@ export class WatchOrchestrator {
201
251
  * Shutdown Orchestrator
202
252
  */
203
253
  async shutdown(): Promise<void> {
204
- if (this._packages.length === 0) {
254
+ if (this._packages.length === 0 && this._watchHookPackages.length === 0) {
205
255
  return;
206
256
  }
207
257
 
208
258
  process.stdout.write("⏳ Shutting down...\n");
209
259
 
210
- await Promise.all([
211
- this._libraryBuilder.shutdown(),
212
- this._dtsBuilder.shutdown(),
213
- ...this._copySrcWatchers.map((w) => w.close()),
214
- ]);
260
+ const shutdownTasks: Array<Promise<void>> = [];
261
+
262
+ if (this._packages.length > 0) {
263
+ shutdownTasks.push(this._libraryBuilder.shutdown());
264
+ shutdownTasks.push(this._dtsBuilder.shutdown());
265
+ shutdownTasks.push(...this._copySrcWatchers.map((w) => w.close()));
266
+ }
267
+
268
+ shutdownTasks.push(...this._watchHookWatchers.map((w) => w.close()));
269
+
270
+ await Promise.all(shutdownTasks);
215
271
  this._copySrcWatchers = [];
272
+ this._watchHookWatchers.length = 0;
216
273
  this._replaceDepWatcher?.dispose();
217
274
 
218
275
  process.stdout.write("✔ Done\n");
@@ -214,13 +214,27 @@ export interface SdServerPackageConfig {
214
214
  }
215
215
 
216
216
  /**
217
- * Scripts-only package configuration (excluded from watch/typecheck)
217
+ * Watch hook configuration for scripts packages
218
+ */
219
+ export interface SdWatchHookConfig {
220
+ /** glob patterns to watch (relative to package directory) */
221
+ target: string[];
222
+ /** command to execute on change */
223
+ cmd: string;
224
+ /** command arguments */
225
+ args?: string[];
226
+ }
227
+
228
+ /**
229
+ * Scripts-only package configuration (excluded from watch/typecheck unless watch hook is configured)
218
230
  */
219
231
  export interface SdScriptsPackageConfig {
220
232
  /** build target */
221
233
  target: "scripts";
222
234
  /** publish configuration */
223
235
  publish?: SdPublishConfig;
236
+ /** watch hook configuration (when set, package is included in watch mode) */
237
+ watch?: SdWatchHookConfig;
224
238
  }
225
239
 
226
240
  /**
@@ -107,8 +107,8 @@ export function filterPackagesByTargets(
107
107
  for (const [name, config] of Object.entries(packages)) {
108
108
  if (config == null) continue;
109
109
 
110
- // Exclude scripts target from watch/dev targets
111
- if (config.target === "scripts") continue;
110
+ // Exclude scripts target unless watch hook is configured
111
+ if (config.target === "scripts" && config.watch == null) continue;
112
112
 
113
113
  // If targets is empty, include all packages
114
114
  if (targets.length === 0) {
@@ -293,6 +293,10 @@ export interface ViteConfigOptions {
293
293
  replaceDeps?: string[];
294
294
  /** Callback when replaceDeps package dist changes */
295
295
  onScopeRebuild?: () => void;
296
+ /** Override build.outDir (e.g. ".capacitor/www" for Capacitor builds) */
297
+ outDir?: string;
298
+ /** Override base path (e.g. "./" for Capacitor builds) */
299
+ base?: string;
296
300
  }
297
301
 
298
302
  /**
@@ -319,7 +323,7 @@ export function createViteConfig(options: ViteConfigOptions): ViteUserConfig {
319
323
 
320
324
  const config: ViteUserConfig = {
321
325
  root: pkgDir,
322
- base: `/${name}/`,
326
+ base: options.base ?? `/${name}/`,
323
327
  plugins: [
324
328
  tsconfigPaths({ projects: [tsconfigPath] }),
325
329
  solidPlugin(),
@@ -358,6 +362,10 @@ export function createViteConfig(options: ViteConfigOptions): ViteUserConfig {
358
362
  // Process.env substitution (applied to both build and dev modes)
359
363
  config.define = envDefine;
360
364
 
365
+ if (options.outDir != null) {
366
+ config.build = { outDir: options.outDir };
367
+ }
368
+
361
369
  if (mode === "build") {
362
370
  config.logLevel = "silent";
363
371
  } else {
@@ -126,6 +126,11 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
126
126
  );
127
127
 
128
128
  // Create Vite configuration and build
129
+ const isCapacitor = info.config.capacitor != null;
130
+ const outDir = isCapacitor
131
+ ? path.join(info.pkgDir, ".capacitor", "www")
132
+ : undefined;
133
+
129
134
  const viteConfig = createViteConfig({
130
135
  pkgDir: info.pkgDir,
131
136
  name: info.name,
@@ -133,12 +138,15 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
133
138
  compilerOptions,
134
139
  env: info.config.env,
135
140
  mode: "build",
141
+ outDir,
142
+ base: isCapacitor ? "./" : undefined,
136
143
  });
137
144
 
138
145
  await viteBuild(viteConfig);
139
146
 
140
147
  // Generate .config.json
141
- const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
148
+ const confDistDir = outDir ?? path.join(info.pkgDir, "dist");
149
+ const confDistPath = path.join(confDistDir, ".config.json");
142
150
  fs.writeFileSync(confDistPath, JSON.stringify(info.config.configs ?? {}, undefined, 2));
143
151
 
144
152
  return { success: true };
@@ -14,6 +14,7 @@ import { registerCleanupHandlers, applyDebugLevel } from "../utils/worker-utils"
14
14
  export interface ServerRuntimeStartInfo {
15
15
  mainJsPath: string;
16
16
  clientPorts: Record<string, number>;
17
+ env?: Record<string, string>;
17
18
  }
18
19
 
19
20
  /**
@@ -113,6 +114,13 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
113
114
  try {
114
115
  const startTime = performance.now();
115
116
 
117
+ // Inject environment variables into process.env before importing main.js
118
+ if (info.env != null) {
119
+ for (const [key, value] of Object.entries(info.env)) {
120
+ process.env[key] = value;
121
+ }
122
+ }
123
+
116
124
  // Import main.js (must export a server instance)
117
125
  logger.debug("[start] Importing main.js...");
118
126
  let stepStart = performance.now();
@@ -1,6 +1,5 @@
1
1
  .tmp
2
- .tasks
3
- .playwright
2
+ .playwright-cli
4
3
  .coverage
5
4
  __pycache__
6
5
  .claude/worktrees
@@ -16,19 +16,18 @@
16
16
  "check": "sd-cli check",
17
17
  "test": "vitest run",
18
18
  "test:e2e": "vitest run -c vitest-e2e.config.ts",
19
- "postinstall": "playwright install && playwright-cli install --skills"
19
+ "postinstall": "playwright-cli install --skills"
20
20
  },
21
21
  "devDependencies": {
22
- "@simplysm/lint": "~13.0.95",
23
- "@simplysm/sd-cli": "~13.0.95",
24
- "@simplysm/sd-claude": "~13.0.95",
22
+ "@simplysm/lint": "~13.0.97",
23
+ "@simplysm/sd-cli": "~13.0.97",
24
+ "@simplysm/sd-claude": "~13.0.97",
25
+ "@playwright/cli": "^0.1.1",
25
26
  "@types/node": "^20.19.37",
26
27
  "eslint": "^9.39.4",
27
28
  "prettier": "^3.8.1",
28
29
  "typescript": "^5.9.3",
29
30
  "vite-tsconfig-paths": "^6.1.1",
30
31
  "vitest": "^4.1.0",
31
- "@playwright/cli": "^0.1.1",
32
- "playwright": "^1.58.2"
33
32
  }
34
33
  }
@@ -6,13 +6,13 @@
6
6
  "private": true,
7
7
  "dependencies": {
8
8
  "@{{projectName}}/db-main": "workspace:*",
9
- "@simplysm/core-browser": "~13.0.95",
10
- "@simplysm/core-common": "~13.0.95",
11
- "@simplysm/excel": "~13.0.95",
12
- "@simplysm/orm-common": "~13.0.95",
13
- "@simplysm/service-client": "~13.0.95",
14
- "@simplysm/service-common": "~13.0.95",
15
- "@simplysm/solid": "~13.0.95",
9
+ "@simplysm/core-browser": "~13.0.97",
10
+ "@simplysm/core-common": "~13.0.97",
11
+ "@simplysm/excel": "~13.0.97",
12
+ "@simplysm/orm-common": "~13.0.97",
13
+ "@simplysm/service-client": "~13.0.97",
14
+ "@simplysm/service-common": "~13.0.97",
15
+ "@simplysm/solid": "~13.0.97",
16
16
  "@solid-primitives/event-listener": "^2.4.5",
17
17
  "@solidjs/router": "^0.15.4",
18
18
  "@tabler/icons-solidjs": "^3.40.0",
@@ -7,7 +7,7 @@
7
7
  ".": "./src/index.ts"
8
8
  },
9
9
  "dependencies": {
10
- "@simplysm/core-common": "~13.0.95",
11
- "@simplysm/orm-common": "~13.0.95"
10
+ "@simplysm/core-common": "~13.0.97",
11
+ "@simplysm/orm-common": "~13.0.97"
12
12
  }
13
13
  }
@@ -5,11 +5,11 @@
5
5
  "private": true,
6
6
  "dependencies": {
7
7
  "@{{projectName}}/db-main": "workspace:*",
8
- "@simplysm/core-common": "~13.0.95",
9
- "@simplysm/excel": "~13.0.95",
10
- "@simplysm/orm-common": "~13.0.95",
11
- "@simplysm/orm-node": "~13.0.95",
12
- "@simplysm/service-server": "~13.0.95",
8
+ "@simplysm/core-common": "~13.0.97",
9
+ "@simplysm/excel": "~13.0.97",
10
+ "@simplysm/orm-common": "~13.0.97",
11
+ "@simplysm/orm-node": "~13.0.97",
12
+ "@simplysm/service-server": "~13.0.97",
13
13
  "bcrypt": "^6.0.0",
14
14
  "pg": "^8.20.0",
15
15
  "pg-copy-streams": "^7.0.0"
@@ -6,7 +6,7 @@
6
6
  "private": true,
7
7
  "dependencies": {
8
8
  "@{{projectName}}/db-main": "workspace:*",
9
- "@simplysm/orm-node": "~13.0.95",
9
+ "@simplysm/orm-node": "~13.0.97",
10
10
  "bcrypt": "^6.0.0",
11
11
  "playwright": "^1.58.2"
12
12
  },
@@ -38,4 +38,39 @@ describe("filterPackagesByTargets", () => {
38
38
  expect(result["core-node"]).toEqual({ target: "node" });
39
39
  });
40
40
 
41
+ it("includes scripts target when watch option is present", () => {
42
+ const packagesWithWatch: Record<string, SdPackageConfig | undefined> = {
43
+ ...mockPackages,
44
+ "claude-with-watch": {
45
+ target: "scripts",
46
+ watch: {
47
+ target: ["../../.claude/rules/sd-*"],
48
+ cmd: "node",
49
+ args: ["scripts/sync.mjs"],
50
+ },
51
+ },
52
+ };
53
+ const result = filterPackagesByTargets(packagesWithWatch, []);
54
+
55
+ expect(result["claude-with-watch"]).toBeDefined();
56
+ expect(result["claude"]).toBeUndefined(); // scripts without watch still excluded
57
+ });
58
+
59
+ it("includes scripts+watch target when specified in targets", () => {
60
+ const packagesWithWatch: Record<string, SdPackageConfig | undefined> = {
61
+ ...mockPackages,
62
+ "claude-with-watch": {
63
+ target: "scripts",
64
+ watch: {
65
+ target: ["../../.claude/rules/sd-*"],
66
+ cmd: "node",
67
+ },
68
+ },
69
+ };
70
+ const result = filterPackagesByTargets(packagesWithWatch, ["claude-with-watch"]);
71
+
72
+ expect(Object.keys(result)).toHaveLength(1);
73
+ expect(result["claude-with-watch"]).toBeDefined();
74
+ });
75
+
41
76
  });