@maligator/cli 0.1.0-alpha.4 → 0.1.0-alpha.5

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/README.md CHANGED
@@ -50,11 +50,116 @@ maligator init
50
50
  maligator doctor [--verbose] [--target rust-triple]
51
51
  maligator build [entry] [--production] [--artifact directory] [--target rust-triple] [--config path]
52
52
  maligator run [entry] [--config path] [-- args...]
53
+ maligator test [path ...] [--run name] [--shuffle [seed]] [--repeat count] [--bail]
53
54
  ```
54
55
 
55
56
  Run commands from the project root. Maligator does not search parent directories
56
57
  for configuration.
57
58
 
59
+ ## Testing
60
+
61
+ `maligator test` discovers conventional JavaScript and erasable-TypeScript test
62
+ files and runs them through Maligator's embedded interpreter. Test runs do not
63
+ generate C or invoke the native toolchain. Import the Maligator-owned API from
64
+ `maligator:test`:
65
+
66
+ ```typescript
67
+ import { beforeEach, describe, expect, test } from "maligator:test";
68
+ import { createRouter, RouteError, type Router } from "./router.ts";
69
+
70
+ describe("router", () => {
71
+ let router: Router;
72
+
73
+ beforeEach(() => {
74
+ router = createRouter();
75
+ });
76
+
77
+ test("matches parameters", async () => {
78
+ await expect(router.match("/users/42")).resolves.toMatchObject({
79
+ id: expect.any(Number),
80
+ });
81
+ });
82
+
83
+ test("rejects malformed parameters", async () => {
84
+ await expect(router.match("/users/nope")).rejects.toThrow(RouteError);
85
+ });
86
+ });
87
+ ```
88
+
89
+ By default, discovery includes `*.test.js`, `*.test.mjs`, `*.test.ts`,
90
+ `*.test.mts`, and the corresponding `*.spec.*` names. Pass files, directories,
91
+ or both:
92
+
93
+ ```shell
94
+ maligator test
95
+ maligator test src/router
96
+ maligator test src/router/router.test.ts
97
+ maligator test --run "router > parameters"
98
+ maligator test --shuffle
99
+ maligator test --shuffle 18492
100
+ maligator test --repeat 10
101
+ maligator test --bail
102
+ ```
103
+
104
+ `--run` filters hierarchical suite and test names. `--shuffle [seed]` changes
105
+ execution order reproducibly and always prints the seed. `--repeat` executes the
106
+ selected tests again while reusing unchanged compilation artifacts. The default
107
+ policy completes the selected suite; `--bail` stops scheduling after the first
108
+ failure.
109
+
110
+ The authoring API includes nested `describe` suites, `beforeAll`, `afterAll`,
111
+ `beforeEach`, `afterEach`, synchronous and promise-returning callbacks,
112
+ `test.skip`, `test.todo`, `test.only`, and `test.each`. A committed `.only`
113
+ focuses execution and prints a warning. Matchers include identity, structural
114
+ equality, truthiness, containment, length, string/regular-expression matching,
115
+ partial-object matching, throwing, `.not`, `.resolves`, `.rejects`, and the
116
+ `expect.any`, `expect.anything`, `expect.stringMatching`,
117
+ `expect.objectContaining`, and `expect.arrayContaining` asymmetric helpers.
118
+ Maligator owns this API and does not claim Jest, Vitest, or `node:test`
119
+ compatibility.
120
+
121
+ Test files currently execute serially in one Realm. Registration and lifecycle
122
+ state are separated by test file, but globals, prototypes, imported module
123
+ singletons, host state, and asynchronous work left running by a test can be
124
+ shared. Do not rely on process-per-file isolation.
125
+
126
+ Frontend bytecode is content-addressed by source, transitive dependencies,
127
+ resolved module identities, relevant build configuration, and compiler/runtime
128
+ format versions. A warm run restores a shared base plus independently cached
129
+ test-file registration fragments. Test results are never cached: every selected
130
+ test executes on every invocation.
131
+
132
+ The package ships documented declarations for `maligator:test`. Projects whose
133
+ TypeScript configuration does not include `maligator.build.ts` should load the
134
+ package declarations explicitly:
135
+
136
+ ```json
137
+ {
138
+ "compilerOptions": {
139
+ "types": ["@maligator/cli"]
140
+ }
141
+ }
142
+ ```
143
+
144
+ ## Build cache
145
+
146
+ Normal `maligator build` and `maligator run` commands use the same portable VM
147
+ definition format for their frontend cache. On a valid hit, Maligator skips
148
+ parsing, module-graph construction, semantic analysis, optimization, register
149
+ allocation, and VM lowering, then emits the same native translation units from
150
+ the restored definition. Generated translation units and the native driver are
151
+ compiled into independently content-addressed objects, so unchanged objects can
152
+ be relinked without repeating C compilation. The final link still runs, and
153
+ native runtime and Rust archives remain separately content-addressed.
154
+
155
+ Build cache invalidation includes source and transitive dependency content,
156
+ package-resolution metadata, relevant configuration, the TypeScript erasure
157
+ frontend, and compiler/wire versions. Generated-object identity additionally
158
+ includes the exact C source, source identity, native headers/runtime artifact,
159
+ compiler/toolchain, target, flags, and build environment. Compiler diagnostic
160
+ dump flags deliberately recompile the frontend because they require live
161
+ semantic and IR objects.
162
+
58
163
  ## Production builds
59
164
 
60
165
  Use production mode for a deployable binary:
package/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /// <reference lib="dom" />
2
2
 
3
+ import "./test-api.d.ts";
4
+
3
5
  export type MaligatorIntlFeature =
4
6
  | "collator"
5
7
  | "number-format"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maligator/cli",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.5",
4
4
  "description": "Ahead-of-time JavaScript compiler and native runtime",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/dirkdev98/maligator#readme",
@@ -30,6 +30,7 @@
30
30
  "bin/maligator.js",
31
31
  "index.js",
32
32
  "index.d.ts",
33
+ "test-api.d.ts",
33
34
  "README.md",
34
35
  "LICENSE"
35
36
  ],
@@ -44,6 +45,6 @@
44
45
  }
45
46
  },
46
47
  "optionalDependencies": {
47
- "@maligator/cli-darwin-arm64": "0.1.0-alpha.4"
48
+ "@maligator/cli-darwin-arm64": "0.1.0-alpha.5"
48
49
  }
49
50
  }
package/test-api.d.ts ADDED
@@ -0,0 +1,167 @@
1
+ declare module "maligator:test" {
2
+ /**
3
+ * A test or hook body. Maligator waits for a returned promise or thenable
4
+ * before advancing the lifecycle.
5
+ */
6
+ export type TestCallback = () => unknown;
7
+ /** A lifecycle hook body, with the same async completion contract as a test. */
8
+ export type HookCallback = TestCallback;
9
+ /** A constructable value accepted by {@link expect.any}. */
10
+ export type Constructor = abstract new (...args: Array<never>) => unknown;
11
+
12
+ /**
13
+ * Opaque partial-match value produced by helpers such as
14
+ * {@link expect.objectContaining}. It may be nested inside `toEqual`,
15
+ * `toStrictEqual`, and `toMatchObject` expectations.
16
+ */
17
+ export interface AsymmetricMatcher {
18
+ readonly __maligator_asymmetric__: string;
19
+ }
20
+
21
+ /** Matchers for a synchronously received value. */
22
+ export interface Matchers {
23
+ /** Negate the following matcher. */
24
+ readonly not: Matchers;
25
+ /** Wait for the received promise to fulfill, then match its value. */
26
+ readonly resolves: AsyncMatchers;
27
+ /** Wait for the received promise to reject, then match its reason. */
28
+ readonly rejects: AsyncMatchers;
29
+ /** Require ECMAScript `Object.is` identity. */
30
+ toBe(expected: unknown): void;
31
+ /** Recursively compare enumerable object properties and array elements. */
32
+ toEqual(expected: unknown): void;
33
+ /**
34
+ * Recursively compare values while also requiring matching prototypes and
35
+ * matching sparse-array holes.
36
+ */
37
+ toStrictEqual(expected: unknown): void;
38
+ /** Require a value other than `undefined`. */
39
+ toBeDefined(): void;
40
+ /** Require `undefined`. */
41
+ toBeUndefined(): void;
42
+ /** Require `null`. */
43
+ toBeNull(): void;
44
+ /** Require a truthy value. */
45
+ toBeTruthy(): void;
46
+ /** Require a falsy value. */
47
+ toBeFalsy(): void;
48
+ /** Require a string substring or an array element matched by identity. */
49
+ toContain(expected: unknown): void;
50
+ /** Require a numeric `.length` equal to `expected`. */
51
+ toHaveLength(expected: number): void;
52
+ /** Match a string against a substring or regular expression. */
53
+ toMatch(expected: string | RegExp): void;
54
+ /** Recursively require the enumerable properties present in `expected`. */
55
+ toMatchObject(expected: object): void;
56
+ /**
57
+ * Invoke the received function and require it to throw. The optional
58
+ * expectation may be a message substring, regular expression, error
59
+ * constructor, or error instance.
60
+ */
61
+ toThrow(
62
+ expected?:
63
+ | string
64
+ | RegExp
65
+ | Error
66
+ | (abstract new (...args: Array<never>) => Error),
67
+ ): void;
68
+ }
69
+
70
+ /**
71
+ * Promise-returning matcher surface exposed by {@link Matchers.resolves} and
72
+ * {@link Matchers.rejects}. Await these calls so the test cannot finish before
73
+ * the assertion.
74
+ */
75
+ export interface AsyncMatchers {
76
+ /** Negate the following asynchronous matcher. */
77
+ readonly not: AsyncMatchers;
78
+ toBe(expected: unknown): Promise<void>;
79
+ toEqual(expected: unknown): Promise<void>;
80
+ toStrictEqual(expected: unknown): Promise<void>;
81
+ toBeDefined(): Promise<void>;
82
+ toBeUndefined(): Promise<void>;
83
+ toBeNull(): Promise<void>;
84
+ toBeTruthy(): Promise<void>;
85
+ toBeFalsy(): Promise<void>;
86
+ toContain(expected: unknown): Promise<void>;
87
+ toHaveLength(expected: number): Promise<void>;
88
+ toMatch(expected: string | RegExp): Promise<void>;
89
+ toMatchObject(expected: object): Promise<void>;
90
+ toThrow(
91
+ expected?:
92
+ | string
93
+ | RegExp
94
+ | Error
95
+ | (abstract new (...args: Array<never>) => Error),
96
+ ): Promise<void>;
97
+ }
98
+
99
+ /** Assertion entrypoint and Maligator-owned asymmetric matcher factories. */
100
+ export interface ExpectFunction {
101
+ /** Create matchers for `received`. The assertion position is captured here. */
102
+ (received: unknown): Matchers;
103
+ /** Match a primitive of the corresponding built-in kind or an instance. */
104
+ any(constructorValue: Constructor): AsymmetricMatcher;
105
+ /** Match any value except `null` and `undefined`. */
106
+ anything(): AsymmetricMatcher;
107
+ /** Match a string containing `pattern` or satisfying the regular expression. */
108
+ stringMatching(pattern: string | RegExp): AsymmetricMatcher;
109
+ /** Match an object containing all recursively matched properties in `value`. */
110
+ objectContaining(value: object): AsymmetricMatcher;
111
+ /** Match an array containing a match for every element in `value`. */
112
+ arrayContaining(value: Array<unknown>): AsymmetricMatcher;
113
+ }
114
+
115
+ /** Register tests in the current suite during module evaluation. */
116
+ export interface TestFunction {
117
+ /** Register a test. Returned promises are awaited by the runner. */
118
+ (name: string, callback: TestCallback): void;
119
+ /** Register a skipped test without invoking its callback. */
120
+ skip(name: string, callback: TestCallback): void;
121
+ /** Register a named placeholder with no callback. */
122
+ todo(name: string): void;
123
+ /**
124
+ * Register a focused test. When any `.only` exists, non-focused tests are
125
+ * skipped and the runner emits a warning.
126
+ */
127
+ only(name: string, callback: TestCallback): void;
128
+ /**
129
+ * Register one test for each row. Use `%#` in `name` for the zero-based row
130
+ * index. Array rows are spread into callback parameters.
131
+ */
132
+ each<const Row extends ReadonlyArray<unknown>>(
133
+ rows: ReadonlyArray<Row>,
134
+ ): (name: string, callback: (...values: [...Row]) => unknown) => void;
135
+ }
136
+
137
+ /** Register nested suites synchronously during module evaluation. */
138
+ export interface DescribeFunction {
139
+ /** Register a suite. Suite callbacks must not return a promise. */
140
+ (name: string, callback: () => void): void;
141
+ /** Register a suite whose descendants are skipped. */
142
+ skip(name: string, callback: () => void): void;
143
+ /** Register a focused suite and emit the runner's focused-test warning. */
144
+ only(name: string, callback: () => void): void;
145
+ }
146
+
147
+ /** Register a test in the current suite. */
148
+ export const test: TestFunction;
149
+ /** Register a nested suite in the current suite. */
150
+ export const describe: DescribeFunction;
151
+ /** Create fluent matchers for a received value. */
152
+ export const expect: ExpectFunction;
153
+ /** Run once before tests in the current suite. */
154
+ export function beforeAll(callback: HookCallback): void;
155
+ /** Run once after tests in the current suite, including after test failures. */
156
+ export function afterAll(callback: HookCallback): void;
157
+ /**
158
+ * Run before every selected descendant test. Ancestor hooks run before hooks
159
+ * declared by a nested suite.
160
+ */
161
+ export function beforeEach(callback: HookCallback): void;
162
+ /**
163
+ * Run after every selected descendant test. Nested-suite hooks run before
164
+ * ancestor hooks.
165
+ */
166
+ export function afterEach(callback: HookCallback): void;
167
+ }