@reliverse/dler 2.2.17 → 2.3.1

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.
@@ -0,0 +1,157 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { readdir } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { relico } from "@reliverse/relico";
6
+ import { defineCommand, loadConfig, option } from "@reliverse/rempts-core";
7
+ import { type } from "arktype";
8
+ export default defineCommand({
9
+ description: "Run tests for your CLI",
10
+ alias: "t",
11
+ options: {
12
+ pattern: option(type("string | string[] | undefined"), {
13
+ short: "p",
14
+ description: "Test file patterns"
15
+ }),
16
+ watch: option(type("boolean | undefined"), {
17
+ short: "w",
18
+ description: "Watch for changes"
19
+ }),
20
+ coverage: option(type("boolean | undefined"), {
21
+ short: "c",
22
+ description: "Generate coverage report"
23
+ }),
24
+ bail: option(type("boolean | undefined"), {
25
+ short: "b",
26
+ description: "Stop on first failure"
27
+ }),
28
+ timeout: option(type("number | undefined"), {
29
+ description: "Test timeout in milliseconds"
30
+ }),
31
+ all: option(type("boolean | undefined"), {
32
+ description: "Run tests in all packages (workspace mode)"
33
+ })
34
+ },
35
+ handler: async ({ flags, positional, spinner, colors }) => {
36
+ const config = await loadConfig();
37
+ if (flags.all && config.workspace?.packages) {
38
+ const packages = config.workspace.packages;
39
+ let allPassed = true;
40
+ for (const packagePattern of packages) {
41
+ const packageDirs = await findPackageDirectories(packagePattern);
42
+ for (const packageDir of packageDirs) {
43
+ const spin = spinner(`Testing ${packageDir}...`);
44
+ spin.start();
45
+ try {
46
+ const result = await runTests(packageDir, flags, config);
47
+ if (result.success) {
48
+ spin.succeed(`${packageDir} tests passed`);
49
+ } else {
50
+ spin.fail(`${packageDir} tests failed`);
51
+ allPassed = false;
52
+ if (flags.bail) {
53
+ break;
54
+ }
55
+ }
56
+ } catch (error) {
57
+ spin.fail(`${packageDir} tests failed`);
58
+ console.error(relico.red(error instanceof Error ? error.message : String(error)));
59
+ allPassed = false;
60
+ if (flags.bail) {
61
+ break;
62
+ }
63
+ }
64
+ }
65
+ if (!allPassed && flags.bail) {
66
+ break;
67
+ }
68
+ }
69
+ if (!allPassed) {
70
+ process.exit(1);
71
+ }
72
+ } else {
73
+ const spin = spinner("Running tests...");
74
+ spin.start();
75
+ try {
76
+ const result = await runTests(".", flags, config);
77
+ if (result.success) {
78
+ spin.succeed("All tests passed!");
79
+ console.log(relico.green(`\u2713 ${result.passed} tests passed`));
80
+ if (result.skipped > 0) {
81
+ console.log(relico.yellow(`\u229D ${result.skipped} tests skipped`));
82
+ }
83
+ } else {
84
+ spin.fail("Tests failed");
85
+ console.log(relico.red(`\u2717 ${result.failed} tests failed`));
86
+ process.exit(1);
87
+ }
88
+ } catch (error) {
89
+ spin.fail("Tests failed");
90
+ console.error(relico.red(error instanceof Error ? error.message : String(error)));
91
+ process.exit(1);
92
+ }
93
+ }
94
+ }
95
+ });
96
+ async function runTests(cwd, flags, config) {
97
+ return new Promise((resolve, reject) => {
98
+ const args = ["test"];
99
+ const patterns = flags.pattern || config.test?.pattern || "**/*.test.ts";
100
+ const patternArray = Array.isArray(patterns) ? patterns : [patterns];
101
+ args.push(...patternArray);
102
+ if (flags.watch ?? config.test?.watch) {
103
+ args.push("--watch");
104
+ }
105
+ if (flags.coverage ?? config.test?.coverage) {
106
+ args.push("--coverage");
107
+ }
108
+ if (flags.bail) {
109
+ args.push("--bail");
110
+ }
111
+ if (flags.timeout) {
112
+ args.push("--timeout", flags.timeout.toString());
113
+ }
114
+ const proc = spawn("bun", args, {
115
+ cwd,
116
+ stdio: ["inherit", "pipe", "pipe"],
117
+ env: {
118
+ ...process.env,
119
+ NODE_ENV: "test"
120
+ }
121
+ });
122
+ let stdout = "";
123
+ let _stderr = "";
124
+ proc.stdout?.on("data", (data) => {
125
+ stdout += data.toString();
126
+ process.stdout.write(data);
127
+ });
128
+ proc.stderr?.on("data", (data) => {
129
+ _stderr += data.toString();
130
+ process.stderr.write(data);
131
+ });
132
+ proc.on("exit", (code) => {
133
+ const passed = (stdout.match(/✓/g) || []).length;
134
+ const failed = (stdout.match(/✗/g) || []).length;
135
+ const skipped = (stdout.match(/⊝/g) || []).length;
136
+ resolve({
137
+ success: code === 0,
138
+ passed,
139
+ failed,
140
+ skipped
141
+ });
142
+ });
143
+ proc.on("error", reject);
144
+ });
145
+ }
146
+ async function findPackageDirectories(pattern) {
147
+ if (pattern.endsWith("/*")) {
148
+ const baseDir = pattern.slice(0, -2);
149
+ if (existsSync(baseDir)) {
150
+ const entries = await readdir(baseDir, { withFileTypes: true });
151
+ return entries.filter(
152
+ (entry) => entry.isDirectory() && existsSync(path.join(baseDir, entry.name, "package.json"))
153
+ ).map((entry) => path.join(baseDir, entry.name));
154
+ }
155
+ }
156
+ return [];
157
+ }
@@ -1,9 +1,9 @@
1
1
  import type { PackageInfo } from "./impl.js";
2
2
  import type { PackageCacheEntry, TscCacheOptions } from "./types.js";
3
3
  export declare class TscCache {
4
- private options;
4
+ private readonly options;
5
5
  private metadata;
6
- private metadataPath;
6
+ private readonly metadataPath;
7
7
  constructor(options?: Partial<TscCacheOptions>);
8
8
  initialize(): Promise<void>;
9
9
  private loadMetadata;
@@ -17,11 +17,13 @@ export class TscCache {
17
17
  this.metadataPath = join(this.options.cacheDir, "metadata.json");
18
18
  }
19
19
  async initialize() {
20
- if (!this.options.enabled) return;
20
+ if (!this.options.enabled) {
21
+ return;
22
+ }
21
23
  try {
22
24
  await mkdir(this.options.cacheDir, { recursive: true });
23
25
  await this.loadMetadata();
24
- } catch (_error) {
26
+ } catch {
25
27
  this.options.enabled = false;
26
28
  }
27
29
  }
@@ -53,14 +55,12 @@ export class TscCache {
53
55
  }
54
56
  }
55
57
  async saveMetadata() {
56
- if (!this.options.enabled || !this.metadata) return;
58
+ if (!(this.options.enabled && this.metadata)) {
59
+ return;
60
+ }
57
61
  try {
58
62
  this.metadata.lastUpdated = Date.now();
59
- await writeFile(
60
- this.metadataPath,
61
- JSON.stringify(this.metadata, null, 2),
62
- "utf-8"
63
- );
63
+ await writeFile(this.metadataPath, JSON.stringify(this.metadata, null, 2), "utf-8");
64
64
  } catch {
65
65
  }
66
66
  }
@@ -92,7 +92,9 @@ export class TscCache {
92
92
  return sourceFiles;
93
93
  }
94
94
  hasSourceFilesChanged(cached, current) {
95
- if (cached.length !== current.length) return true;
95
+ if (cached.length !== current.length) {
96
+ return true;
97
+ }
96
98
  const currentMap = new Map(current.map((file) => [file.path, file]));
97
99
  for (const cachedFile of cached) {
98
100
  const currentFile = currentMap.get(cachedFile.path);
@@ -103,11 +105,17 @@ export class TscCache {
103
105
  return false;
104
106
  }
105
107
  async shouldSkipPackage(pkg) {
106
- if (!this.options.enabled || !this.metadata) return false;
108
+ if (!(this.options.enabled && this.metadata)) {
109
+ return false;
110
+ }
107
111
  const cacheEntry = this.metadata.packages[pkg.name];
108
- if (!cacheEntry || !cacheEntry.lastSuccess) return false;
112
+ if (!cacheEntry?.lastSuccess) {
113
+ return false;
114
+ }
109
115
  const timeSinceLastSuccess = Date.now() - cacheEntry.lastSuccess;
110
- if (timeSinceLastSuccess > this.options.maxAge) return false;
116
+ if (timeSinceLastSuccess > this.options.maxAge) {
117
+ return false;
118
+ }
111
119
  const currentSourceFiles = await this.getSourceFiles(pkg.path);
112
120
  if (this.hasSourceFilesChanged(cacheEntry.sourceFiles, currentSourceFiles)) {
113
121
  return false;
@@ -115,9 +123,13 @@ export class TscCache {
115
123
  return true;
116
124
  }
117
125
  async getCachedResult(pkg) {
118
- if (!this.options.enabled || !this.metadata) return null;
126
+ if (!(this.options.enabled && this.metadata)) {
127
+ return null;
128
+ }
119
129
  const cacheEntry = this.metadata.packages[pkg.name];
120
- if (!cacheEntry) return null;
130
+ if (!cacheEntry) {
131
+ return null;
132
+ }
121
133
  const currentSourceFiles = await this.getSourceFiles(pkg.path);
122
134
  if (this.hasSourceFilesChanged(cacheEntry.sourceFiles, currentSourceFiles)) {
123
135
  return null;
@@ -125,7 +137,9 @@ export class TscCache {
125
137
  return cacheEntry;
126
138
  }
127
139
  async updatePackageCache(pkg, result) {
128
- if (!this.options.enabled || !this.metadata) return;
140
+ if (!(this.options.enabled && this.metadata)) {
141
+ return;
142
+ }
129
143
  const sourceFiles = await this.getSourceFiles(pkg.path);
130
144
  const now = Date.now();
131
145
  this.metadata.packages[pkg.name] = {
@@ -135,13 +149,15 @@ export class TscCache {
135
149
  hasErrors: !result.success,
136
150
  errorCount: result.errorCount,
137
151
  warningCount: result.warningCount,
138
- output: result.output,
139
- filteredOutput: result.filteredOutput
152
+ ...result.output && { output: result.output },
153
+ ...result.filteredOutput && { filteredOutput: result.filteredOutput }
140
154
  };
141
155
  await this.saveMetadata();
142
156
  }
143
157
  async clearCache() {
144
- if (!this.options.enabled) return;
158
+ if (!this.options.enabled) {
159
+ return;
160
+ }
145
161
  this.metadata = {
146
162
  version: CACHE_VERSION,
147
163
  lastUpdated: Date.now(),
@@ -150,7 +166,9 @@ export class TscCache {
150
166
  await this.saveMetadata();
151
167
  }
152
168
  getCacheStats() {
153
- if (!this.metadata) return { totalPackages: 0, successfulPackages: 0 };
169
+ if (!this.metadata) {
170
+ return { totalPackages: 0, successfulPackages: 0 };
171
+ }
154
172
  const totalPackages = Object.keys(this.metadata.packages).length;
155
173
  const successfulPackages = Object.values(this.metadata.packages).filter(
156
174
  (entry) => entry.lastSuccess !== null
@@ -1,2 +1,15 @@
1
- declare const _default: any;
1
+ declare const _default: import("@reliverse/rempts-core").Command<{
2
+ filter: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<string | undefined, {}>>;
3
+ ignore: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<string | undefined, {}>>;
4
+ cwd: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<string | undefined, {}>>;
5
+ concurrency: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<number | undefined, {}>>;
6
+ stopOnError: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
7
+ verbose: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
8
+ copyLogs: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
9
+ cache: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
10
+ incremental: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
11
+ autoConcurrency: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
12
+ skipUnchanged: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
13
+ buildMode: import("@reliverse/rempts-core").CLIOption<import("arktype").BaseType<boolean | undefined, {}>>;
14
+ }, {}, string>;
2
15
  export default _default;
@@ -1,105 +1,69 @@
1
1
  import { logger } from "@reliverse/relinka";
2
- import { defineArgs, defineCommand } from "@reliverse/rempts";
2
+ import { defineCommand, option } from "@reliverse/rempts-core";
3
+ import { type } from "arktype";
3
4
  import { runTscOnAllPackages } from "./impl.js";
4
5
  export default defineCommand({
5
- meta: {
6
- name: "tsc",
7
- description: "Run TypeScript type checking on all workspace packages",
8
- examples: [
9
- "dler tsc",
10
- 'dler tsc --filter "@reliverse/rempts,@reliverse/build"',
11
- 'dler tsc --ignore "@reliverse/*"',
12
- 'dler tsc --ignore "@reliverse/relico" --ignore "@reliverse/dler-v1"',
13
- 'dler tsc --ignore "@reliverse/relico @reliverse/dler-v1"',
14
- "dler tsc --cwd /path/to/monorepo",
15
- "dler tsc --cwd /path/to/monorepo --ignore @reliverse/*",
16
- "dler tsc --concurrency 8",
17
- "dler tsc --concurrency 2 --stopOnError",
18
- "dler tsc --ignore @reliverse/* --concurrency 6 --stopOnError",
19
- "dler tsc --verbose",
20
- "dler tsc --verbose --ignore @reliverse/*",
21
- "dler tsc --verbose --concurrency 2 --stopOnError",
22
- "dler tsc --copy-logs",
23
- "dler tsc --copy-logs --verbose",
24
- "dler tsc --auto-concurrency",
25
- "dler tsc --no-cache",
26
- "dler tsc --no-incremental",
27
- "dler tsc --build-mode",
28
- "dler tsc --skip-unchanged",
29
- "dler tsc --auto-concurrency --build-mode --verbose"
30
- ]
31
- },
32
- args: defineArgs({
33
- filter: {
34
- type: "string",
35
- description: "Package(s) to include (supports wildcards and comma-separated values like '@reliverse/rempts,@reliverse/build'). Takes precedence over --ignore when both are provided.",
36
- positional: true
37
- },
38
- ignore: {
39
- type: "string",
6
+ description: "Run TypeScript type checking on all workspace packages",
7
+ options: {
8
+ filter: option(type("string | undefined"), {
9
+ description: "Package(s) to include (supports wildcards and comma-separated values like 'rempts,@reliverse/build'). Takes precedence over --ignore when both are provided."
10
+ }),
11
+ ignore: option(type("string | undefined"), {
40
12
  description: "Package(s) to ignore (supports wildcards like @reliverse/*)"
41
- },
42
- cwd: {
43
- type: "string",
13
+ }),
14
+ cwd: option(type("string | undefined"), {
44
15
  description: "Working directory (monorepo root)"
45
- },
46
- concurrency: {
47
- type: "number",
16
+ }),
17
+ concurrency: option(type("number | undefined"), {
48
18
  description: "Number of packages to check concurrently (default: CPU cores)"
49
- },
50
- stopOnError: {
51
- type: "boolean",
19
+ }),
20
+ stopOnError: option(type("boolean | undefined"), {
52
21
  description: "Stop on first error instead of collecting all errors (default: false)"
53
- },
54
- verbose: {
55
- type: "boolean",
22
+ }),
23
+ verbose: option(type("boolean | undefined"), {
56
24
  description: "Verbose mode (default: false)"
57
- },
58
- copyLogs: {
59
- type: "boolean",
25
+ }),
26
+ copyLogs: option(type("boolean | undefined"), {
60
27
  description: "Copy failed package logs to clipboard (default: true, skipped in CI)",
61
28
  default: true
62
- },
63
- cache: {
64
- type: "boolean",
29
+ }),
30
+ cache: option(type("boolean | undefined"), {
65
31
  description: "Enable caching for faster subsequent runs (default: true)"
66
- },
67
- incremental: {
68
- type: "boolean",
32
+ }),
33
+ incremental: option(type("boolean | undefined"), {
69
34
  description: "Use TypeScript incremental compilation (default: true)"
70
- },
71
- autoConcurrency: {
72
- type: "boolean",
35
+ }),
36
+ autoConcurrency: option(type("boolean | undefined"), {
73
37
  description: "Auto-detect optimal concurrency based on CPU cores (default: false)"
74
- },
75
- skipUnchanged: {
76
- type: "boolean",
38
+ }),
39
+ skipUnchanged: option(type("boolean | undefined"), {
77
40
  description: "Skip packages with no changes since last check (default: true)"
78
- },
79
- buildMode: {
80
- type: "boolean",
41
+ }),
42
+ buildMode: option(type("boolean | undefined"), {
81
43
  description: "Use tsc --build for project references (default: false)"
82
- }
83
- }),
84
- run: async ({ args }) => {
44
+ })
45
+ },
46
+ handler: async ({ flags }) => {
85
47
  try {
86
48
  if (typeof process.versions.bun === "undefined") {
87
49
  logger.error("\u274C This command requires Bun runtime. Sorry.");
88
50
  process.exit(1);
89
51
  }
90
52
  const isCI = process.env.CI === "true" || !process.stdout.isTTY;
91
- const shouldCopyLogs = args.copyLogs !== false && !isCI;
92
- const results = await runTscOnAllPackages(args.ignore, args.cwd, {
93
- filter: args.filter,
94
- concurrency: args.concurrency,
95
- stopOnError: args.stopOnError,
96
- verbose: args.verbose,
53
+ const shouldCopyLogs = flags.copyLogs !== false && !isCI;
54
+ const results = await runTscOnAllPackages(flags.ignore, flags.cwd, {
97
55
  copyLogs: shouldCopyLogs,
98
- cache: args.cache,
99
- incremental: args.incremental,
100
- autoConcurrency: args.autoConcurrency,
101
- skipUnchanged: args.skipUnchanged,
102
- buildMode: args.buildMode
56
+ ...flags.filter && { filter: flags.filter },
57
+ ...flags.concurrency && { concurrency: flags.concurrency },
58
+ ...flags.stopOnError && { stopOnError: flags.stopOnError },
59
+ ...flags.verbose && { verbose: flags.verbose },
60
+ ...flags.cache && { cache: flags.cache },
61
+ ...flags.incremental && { incremental: flags.incremental },
62
+ ...flags.autoConcurrency && {
63
+ autoConcurrency: flags.autoConcurrency
64
+ },
65
+ ...flags.skipUnchanged && { skipUnchanged: flags.skipUnchanged },
66
+ ...flags.buildMode && { buildMode: flags.buildMode }
103
67
  });
104
68
  if (results.hasErrors) {
105
69
  process.exit(1);