@git.zone/tswatch 2.3.13 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/index.d.ts +4 -1
  3. package/dist_ts/index.js +5 -2
  4. package/dist_ts/interfaces/index.d.ts +1 -1
  5. package/dist_ts/interfaces/index.js +2 -2
  6. package/dist_ts/interfaces/interfaces.config.d.ts +58 -0
  7. package/dist_ts/interfaces/interfaces.config.js +2 -0
  8. package/dist_ts/tswatch.classes.confighandler.d.ts +30 -0
  9. package/dist_ts/tswatch.classes.confighandler.js +172 -0
  10. package/dist_ts/tswatch.classes.tswatch.d.ts +28 -3
  11. package/dist_ts/tswatch.classes.tswatch.js +135 -165
  12. package/dist_ts/tswatch.classes.watcher.d.ts +31 -3
  13. package/dist_ts/tswatch.classes.watcher.js +105 -25
  14. package/dist_ts/tswatch.cli.js +39 -28
  15. package/dist_ts/tswatch.init.d.ts +25 -0
  16. package/dist_ts/tswatch.init.js +168 -0
  17. package/dist_ts/tswatch.plugins.d.ts +3 -1
  18. package/dist_ts/tswatch.plugins.js +7 -5
  19. package/npmextra.json +12 -6
  20. package/package.json +21 -15
  21. package/readme.hints.md +88 -46
  22. package/readme.md +284 -149
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/index.ts +6 -2
  25. package/ts/interfaces/index.ts +1 -1
  26. package/ts/interfaces/interfaces.config.ts +61 -0
  27. package/ts/tswatch.classes.confighandler.ts +185 -0
  28. package/ts/tswatch.classes.tswatch.ts +161 -197
  29. package/ts/tswatch.classes.watcher.ts +134 -23
  30. package/ts/tswatch.cli.ts +37 -31
  31. package/ts/tswatch.init.ts +199 -0
  32. package/ts/tswatch.plugins.ts +7 -3
  33. package/dist_ts/interfaces/interfaces.watchmodes.d.ts +0 -1
  34. package/dist_ts/interfaces/interfaces.watchmodes.js +0 -2
  35. package/ts/interfaces/interfaces.watchmodes.ts +0 -1
@@ -0,0 +1,185 @@
1
+ import * as plugins from './tswatch.plugins.js';
2
+ import * as paths from './tswatch.paths.js';
3
+ import * as interfaces from './interfaces/index.js';
4
+
5
+ const CONFIG_KEY = '@git.zone/tswatch';
6
+
7
+ /**
8
+ * Preset configurations matching legacy watch modes
9
+ */
10
+ const presets: Record<string, interfaces.ITswatchConfig> = {
11
+ npm: {
12
+ watchers: [
13
+ {
14
+ name: 'npm-test',
15
+ watch: ['./ts/**/*', './test/**/*'],
16
+ command: 'npm run test',
17
+ restart: true,
18
+ debounce: 300,
19
+ runOnStart: true,
20
+ },
21
+ ],
22
+ },
23
+ test: {
24
+ watchers: [
25
+ {
26
+ name: 'test2',
27
+ watch: ['./ts/**/*', './test/**/*'],
28
+ command: 'npm run test2',
29
+ restart: true,
30
+ debounce: 300,
31
+ runOnStart: true,
32
+ },
33
+ ],
34
+ },
35
+ service: {
36
+ watchers: [
37
+ {
38
+ name: 'service',
39
+ watch: './ts/**/*',
40
+ command: 'npm run startTs',
41
+ restart: true,
42
+ debounce: 300,
43
+ runOnStart: true,
44
+ },
45
+ ],
46
+ },
47
+ element: {
48
+ server: {
49
+ enabled: true,
50
+ port: 3002,
51
+ serveDir: './dist_watch/',
52
+ liveReload: true,
53
+ },
54
+ bundles: [
55
+ {
56
+ name: 'element-bundle',
57
+ from: './html/index.ts',
58
+ to: './dist_watch/bundle.js',
59
+ watchPatterns: ['./ts_web/**/*'],
60
+ triggerReload: true,
61
+ },
62
+ {
63
+ name: 'html',
64
+ from: './html/index.html',
65
+ to: './dist_watch/index.html',
66
+ watchPatterns: ['./html/**/*'],
67
+ triggerReload: true,
68
+ },
69
+ ],
70
+ watchers: [
71
+ {
72
+ name: 'ts-build',
73
+ watch: './ts/**/*',
74
+ command: 'npm run build',
75
+ restart: false,
76
+ debounce: 300,
77
+ runOnStart: false,
78
+ },
79
+ ],
80
+ },
81
+ website: {
82
+ bundles: [
83
+ {
84
+ name: 'website-bundle',
85
+ from: './ts_web/index.ts',
86
+ to: './dist_serve/bundle.js',
87
+ watchPatterns: ['./ts_web/**/*'],
88
+ triggerReload: false,
89
+ },
90
+ {
91
+ name: 'html',
92
+ from: './html/index.html',
93
+ to: './dist_serve/index.html',
94
+ watchPatterns: ['./html/**/*'],
95
+ triggerReload: false,
96
+ },
97
+ {
98
+ name: 'assets',
99
+ from: './assets/',
100
+ to: './dist_serve/assets/',
101
+ watchPatterns: ['./assets/**/*'],
102
+ triggerReload: false,
103
+ },
104
+ ],
105
+ watchers: [
106
+ {
107
+ name: 'backend',
108
+ watch: './ts/**/*',
109
+ command: 'npm run startTs',
110
+ restart: true,
111
+ debounce: 300,
112
+ runOnStart: true,
113
+ },
114
+ ],
115
+ },
116
+ };
117
+
118
+ /**
119
+ * Handles loading and managing tswatch configuration
120
+ */
121
+ export class ConfigHandler {
122
+ private npmextra: plugins.npmextra.Npmextra;
123
+ private cwd: string;
124
+
125
+ constructor(cwdArg?: string) {
126
+ this.cwd = cwdArg || paths.cwd;
127
+ this.npmextra = new plugins.npmextra.Npmextra(this.cwd);
128
+ }
129
+
130
+ /**
131
+ * Check if a tswatch configuration exists
132
+ */
133
+ public hasConfig(): boolean {
134
+ const config = this.npmextra.dataFor<interfaces.ITswatchConfig>(CONFIG_KEY, null);
135
+ return config !== null;
136
+ }
137
+
138
+ /**
139
+ * Load configuration from npmextra.json
140
+ * If a preset is specified, merge preset defaults with user overrides
141
+ */
142
+ public loadConfig(): interfaces.ITswatchConfig | null {
143
+ const config = this.npmextra.dataFor<interfaces.ITswatchConfig>(CONFIG_KEY, null);
144
+
145
+ if (!config) {
146
+ return null;
147
+ }
148
+
149
+ // If a preset is specified, merge it with user config
150
+ if (config.preset && presets[config.preset]) {
151
+ const preset = presets[config.preset];
152
+ return {
153
+ ...preset,
154
+ ...config,
155
+ // Merge arrays instead of replacing
156
+ watchers: config.watchers || preset.watchers,
157
+ bundles: config.bundles || preset.bundles,
158
+ server: config.server !== undefined ? config.server : preset.server,
159
+ };
160
+ }
161
+
162
+ return config;
163
+ }
164
+
165
+ /**
166
+ * Get a preset configuration by name
167
+ */
168
+ public getPreset(presetName: string): interfaces.ITswatchConfig | null {
169
+ return presets[presetName] || null;
170
+ }
171
+
172
+ /**
173
+ * Get all available preset names
174
+ */
175
+ public getPresetNames(): string[] {
176
+ return Object.keys(presets);
177
+ }
178
+
179
+ /**
180
+ * Get the config key for npmextra.json
181
+ */
182
+ public getConfigKey(): string {
183
+ return CONFIG_KEY;
184
+ }
185
+ }
@@ -3,221 +3,185 @@ import * as paths from './tswatch.paths.js';
3
3
  import * as interfaces from './interfaces/index.js';
4
4
 
5
5
  import { Watcher } from './tswatch.classes.watcher.js';
6
+ import { ConfigHandler } from './tswatch.classes.confighandler.js';
6
7
  import { logger } from './tswatch.logging.js';
7
8
 
8
- // Create smartfs instance for directory operations
9
- const smartfs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
10
-
11
9
  /**
12
- * Lists all folders in a directory
10
+ * TsWatch - Config-driven file watcher
11
+ *
12
+ * Reads configuration from npmextra.json under the key '@git.zone/tswatch'
13
+ * and sets up watchers, bundles, and dev server accordingly.
13
14
  */
14
- const listFolders = async (dirPath: string): Promise<string[]> => {
15
- const entries = await smartfs.directory(dirPath).list();
16
- return entries
17
- .filter((entry) => entry.isDirectory)
18
- .map((entry) => entry.name);
19
- };
20
-
21
15
  export class TsWatch {
22
- public watchmode: interfaces.TWatchModes;
16
+ public config: interfaces.ITswatchConfig;
23
17
  public watcherMap = new plugins.lik.ObjectMap<Watcher>();
24
- public typedserver: plugins.typedserver.TypedServer;
18
+ public typedserver: plugins.typedserver.TypedServer | null = null;
19
+
20
+ private tsbundle = new plugins.tsbundle.TsBundle();
21
+ private htmlHandler = new plugins.tsbundle.HtmlHandler();
22
+ private assetsHandler = new plugins.tsbundle.AssetsHandler();
23
+
24
+ constructor(configArg: interfaces.ITswatchConfig) {
25
+ this.config = configArg;
26
+ }
27
+
28
+ /**
29
+ * Create TsWatch from npmextra.json configuration
30
+ */
31
+ public static fromConfig(cwdArg?: string): TsWatch | null {
32
+ const configHandler = new ConfigHandler(cwdArg);
33
+ const config = configHandler.loadConfig();
34
+
35
+ if (!config) {
36
+ return null;
37
+ }
25
38
 
26
- constructor(watchmodeArg: interfaces.TWatchModes) {
27
- this.watchmode = watchmodeArg;
39
+ return new TsWatch(config);
28
40
  }
29
41
 
30
42
  /**
31
43
  * starts the TsWatch instance
32
44
  */
33
45
  public async start() {
34
- const tsbundle = new plugins.tsbundle.TsBundle();
35
- const assetsHandler = new plugins.tsbundle.AssetsHandler();
36
- const htmlHandler = new plugins.tsbundle.HtmlHandler();
37
- switch (this.watchmode) {
38
- case 'test':
39
- /**
40
- * this strategy runs test whenever there is a change in the ts directory
41
- */
42
- this.watcherMap.add(
43
- new Watcher({
44
- filePathToWatch: paths.cwd,
45
- commandToExecute: 'npm run test2',
46
- timeout: null,
47
- }),
48
- );
49
- break;
50
- case 'node':
51
- this.watcherMap.add(
52
- new Watcher({
53
- filePathToWatch: paths.cwd,
54
- commandToExecute: 'npm run test',
55
- timeout: null,
56
- }),
57
- );
58
- break;
59
- case 'element':
60
- await (async () => {
61
- /**
62
- * this strategy runs a standard server and bundles the ts files to a dist_watch directory
63
- */
64
- // lets create a standard server
65
- logger.log(
66
- 'info',
67
- 'bundling TypeScript files to "dist_watch" Note: This is for development only!',
68
- );
69
- this.typedserver = new plugins.typedserver.TypedServer({
70
- cors: true,
71
- injectReload: true,
72
- serveDir: plugins.path.join(paths.cwd, './dist_watch/'),
73
- port: 3002,
74
- compression: true,
75
- spaFallback: true,
76
- securityHeaders: {
77
- crossOriginOpenerPolicy: 'same-origin',
78
- crossOriginEmbedderPolicy: 'require-corp',
79
- },
80
- });
46
+ logger.log('info', 'Starting tswatch with config-driven mode');
81
47
 
82
- const bundleAndReloadElement = async () => {
83
- await tsbundle.build(paths.cwd, './html/index.ts', './dist_watch/bundle.js', {
84
- bundler: 'esbuild',
85
- });
86
- await this.typedserver.reload();
87
- };
88
- this.watcherMap.add(
89
- new Watcher({
90
- filePathToWatch: plugins.path.join(paths.cwd, './ts_web/'),
91
- functionToCall: async () => {
92
- await bundleAndReloadElement();
93
- },
94
- timeout: null,
95
- }),
96
- );
97
-
98
- // lets get the other ts folders
99
- let tsfolders = await listFolders(paths.cwd);
100
- tsfolders = tsfolders.filter(
101
- (itemArg) => itemArg.startsWith('ts') && itemArg !== 'ts_web',
102
- );
103
- const smartshellInstance = new plugins.smartshell.Smartshell({
104
- executor: 'bash',
105
- });
106
- for (const tsfolder of tsfolders) {
107
- logger.log('info', `creating watcher for folder ${tsfolder}`);
108
- this.watcherMap.add(
109
- new Watcher({
110
- filePathToWatch: plugins.path.join(paths.cwd, `./${tsfolder}/`),
111
- functionToCall: async () => {
112
- logger.log('info', `building ${tsfolder}`);
113
- await smartshellInstance.exec(`(cd ${paths.cwd} && npm run build)`);
114
- await bundleAndReloadElement();
115
- },
116
- timeout: null,
117
- }),
118
- );
119
- }
120
-
121
- this.watcherMap.add(
122
- new Watcher({
123
- filePathToWatch: plugins.path.join(paths.cwd, './html/'),
124
- functionToCall: async () => {
125
- await htmlHandler.processHtml({
126
- from: plugins.path.join(paths.cwd, './html/index.html'),
127
- to: plugins.path.join(paths.cwd, './dist_watch/index.html'),
128
- minify: false,
129
- });
130
- await bundleAndReloadElement();
131
- },
132
- timeout: null,
133
- }),
134
- );
135
- })();
136
- break;
137
- case 'website':
138
- await (async () => {
139
- const websiteExecution = new plugins.smartshell.SmartExecution('npm run startTs');
140
- const bundleAndReloadWebsite = async () => {
141
- await tsbundle.build(paths.cwd, './ts_web/index.ts', './dist_serve/bundle.js', {
142
- bundler: 'esbuild',
143
- });
144
- };
145
- let tsfolders = await listFolders(paths.cwd);
146
- tsfolders = tsfolders.filter(
147
- (itemArg) => itemArg.startsWith('ts') && itemArg !== 'ts_web',
148
- );
149
- for (const tsfolder of tsfolders) {
150
- this.watcherMap.add(
151
- new Watcher({
152
- filePathToWatch: plugins.path.join(paths.cwd, `./${tsfolder}/`),
153
- functionToCall: async () => {
154
- await websiteExecution.restart();
155
- await bundleAndReloadWebsite();
156
- },
157
- timeout: null,
158
- }),
159
- );
160
- }
161
- this.watcherMap.add(
162
- new Watcher({
163
- filePathToWatch: plugins.path.join(paths.cwd, './ts_web/'),
164
- functionToCall: async () => {
165
- await bundleAndReloadWebsite();
166
- },
167
- timeout: null,
168
- }),
169
- );
170
- this.watcherMap.add(
171
- new Watcher({
172
- filePathToWatch: plugins.path.join(paths.cwd, './html/'),
173
- functionToCall: async () => {
174
- await htmlHandler.processHtml({
175
- from: plugins.path.join(paths.cwd, './html/index.html'),
176
- to: plugins.path.join(paths.cwd, './dist_serve/index.html'),
177
- minify: false,
178
- });
179
- await bundleAndReloadWebsite();
180
- },
181
- timeout: null,
182
- }),
183
- );
184
- this.watcherMap.add(
185
- new Watcher({
186
- filePathToWatch: plugins.path.join(paths.cwd, './assets/'),
187
- functionToCall: async () => {
188
- await assetsHandler.processAssets();
189
- await bundleAndReloadWebsite();
190
- },
191
- timeout: null,
192
- }),
193
- );
194
- })();
195
- break;
196
- case 'service':
197
- this.watcherMap.add(
198
- new Watcher({
199
- filePathToWatch: plugins.path.join(paths.cwd, './ts/'),
200
- commandToExecute: 'npm run startTs',
201
- timeout: null,
202
- }),
203
- );
204
- break;
205
- case 'echo':
206
- const tsWatchInstanceEchoSomething = new Watcher({
207
- filePathToWatch: plugins.path.join(paths.cwd, './ts'),
208
- commandToExecute: 'npm -v',
209
- timeout: null,
210
- });
211
- this.watcherMap.add(tsWatchInstanceEchoSomething);
212
- break;
213
- default:
214
- break;
48
+ // Start server if configured
49
+ if (this.config.server?.enabled) {
50
+ await this.startServer();
215
51
  }
216
- this.watcherMap.forEach(async (watcher) => {
52
+
53
+ // Setup bundles and their watchers
54
+ if (this.config.bundles && this.config.bundles.length > 0) {
55
+ await this.setupBundles();
56
+ }
57
+
58
+ // Setup watchers from config
59
+ if (this.config.watchers && this.config.watchers.length > 0) {
60
+ await this.setupWatchers();
61
+ }
62
+
63
+ // Start all watchers
64
+ await this.watcherMap.forEach(async (watcher) => {
217
65
  await watcher.start();
218
66
  });
67
+
68
+ // Start server after watchers are ready
219
69
  if (this.typedserver) {
220
70
  await this.typedserver.start();
71
+ logger.log('ok', `Dev server started on port ${this.config.server?.port || 3002}`);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Start the development server
77
+ */
78
+ private async startServer() {
79
+ const serverConfig = this.config.server!;
80
+ const port = serverConfig.port || 3002;
81
+ const serveDir = serverConfig.serveDir || './dist_watch/';
82
+
83
+ logger.log('info', `Setting up dev server on port ${port}, serving ${serveDir}`);
84
+
85
+ this.typedserver = new plugins.typedserver.TypedServer({
86
+ cors: true,
87
+ injectReload: serverConfig.liveReload !== false,
88
+ serveDir: plugins.path.join(paths.cwd, serveDir),
89
+ port: port,
90
+ compression: true,
91
+ spaFallback: true,
92
+ securityHeaders: {
93
+ crossOriginOpenerPolicy: 'same-origin',
94
+ crossOriginEmbedderPolicy: 'require-corp',
95
+ },
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Setup bundle watchers
101
+ */
102
+ private async setupBundles() {
103
+ for (const bundleConfig of this.config.bundles!) {
104
+ const name = bundleConfig.name || `bundle-${bundleConfig.from}`;
105
+ logger.log('info', `Setting up bundle: ${name}`);
106
+
107
+ // Determine what patterns to watch
108
+ const watchPatterns = bundleConfig.watchPatterns || [
109
+ plugins.path.dirname(bundleConfig.from) + '/**/*',
110
+ ];
111
+
112
+ // Create the bundle function
113
+ const bundleFunction = async () => {
114
+ logger.log('info', `[${name}] bundling...`);
115
+
116
+ // Determine bundle type based on file extension
117
+ const fromPath = bundleConfig.from;
118
+ const toPath = bundleConfig.to;
119
+
120
+ if (fromPath.endsWith('.html')) {
121
+ // HTML processing
122
+ await this.htmlHandler.processHtml({
123
+ from: plugins.path.join(paths.cwd, fromPath),
124
+ to: plugins.path.join(paths.cwd, toPath),
125
+ minify: false,
126
+ });
127
+ } else if (fromPath.endsWith('/') || !fromPath.includes('.')) {
128
+ // Assets directory copy
129
+ await this.assetsHandler.processAssets();
130
+ } else {
131
+ // TypeScript bundling
132
+ await this.tsbundle.build(paths.cwd, fromPath, toPath, {
133
+ bundler: 'esbuild',
134
+ });
135
+ }
136
+
137
+ logger.log('ok', `[${name}] bundle complete`);
138
+
139
+ // Trigger reload if configured and server is running
140
+ if (bundleConfig.triggerReload !== false && this.typedserver) {
141
+ await this.typedserver.reload();
142
+ }
143
+ };
144
+
145
+ // Run initial bundle
146
+ await bundleFunction();
147
+
148
+ // Create watcher for this bundle
149
+ this.watcherMap.add(
150
+ new Watcher({
151
+ name: name,
152
+ filePathToWatch: watchPatterns.map((p) => plugins.path.join(paths.cwd, p)),
153
+ functionToCall: bundleFunction,
154
+ runOnStart: false, // Already ran above
155
+ debounce: 300,
156
+ }),
157
+ );
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Setup watchers from config
163
+ */
164
+ private async setupWatchers() {
165
+ for (const watcherConfig of this.config.watchers!) {
166
+ logger.log('info', `Setting up watcher: ${watcherConfig.name}`);
167
+
168
+ // Convert watch paths to absolute
169
+ const watchPaths = Array.isArray(watcherConfig.watch)
170
+ ? watcherConfig.watch
171
+ : [watcherConfig.watch];
172
+
173
+ const absolutePaths = watchPaths.map((p) => plugins.path.join(paths.cwd, p));
174
+
175
+ this.watcherMap.add(
176
+ new Watcher({
177
+ name: watcherConfig.name,
178
+ filePathToWatch: absolutePaths,
179
+ commandToExecute: watcherConfig.command,
180
+ restart: watcherConfig.restart ?? true,
181
+ debounce: watcherConfig.debounce ?? 300,
182
+ runOnStart: watcherConfig.runOnStart ?? true,
183
+ }),
184
+ );
221
185
  }
222
186
  }
223
187
 
@@ -228,7 +192,7 @@ export class TsWatch {
228
192
  if (this.typedserver) {
229
193
  await this.typedserver.stop();
230
194
  }
231
- this.watcherMap.forEach(async (watcher) => {
195
+ await this.watcherMap.forEach(async (watcher) => {
232
196
  await watcher.stop();
233
197
  });
234
198
  }