four-stroke 0.2.2 → 0.4.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.
package/bin/api-types.mjs CHANGED
@@ -1,15 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import ts from "@typescript/typescript6";
3
4
  import fs from "node:fs";
4
- import openapiTS from "openapi-typescript";
5
5
  import { format } from "oxfmt";
6
- import ts from "typescript";
6
+ import openapiTS from "../src/openapi-typescript.mjs";
7
7
 
8
8
  const services = process.argv[2].split(",");
9
9
 
10
10
  const UUID = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("UUID"));
11
11
  const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
12
12
 
13
+ const isEmpty = (node) =>
14
+ ts.isTypeAliasDeclaration(node) &&
15
+ ts.isTypeReferenceNode(node.type) &&
16
+ ts.isIdentifier(node.type.typeName) &&
17
+ node.type.typeName.text === "Record" &&
18
+ node.type.typeArguments?.length === 2 &&
19
+ node.type.typeArguments[1].kind === ts.SyntaxKind.NeverKeyword;
20
+
13
21
  await Promise.all(
14
22
  services.map(async (service) => {
15
23
  const output = await openapiTS(`${service}/doc`, {
@@ -20,18 +28,18 @@ await Promise.all(
20
28
  return null;
21
29
  },
22
30
  });
31
+ const path = `src/__definitions__/${service.replace("https://", "").replace("http://", "")}-definitions.ts`;
23
32
  const printer = ts.createPrinter({});
24
- const resultFile = ts.createSourceFile(
25
- `src/__definitions__/${service.replace("https://", "")}-definitions.ts`,
26
- "",
27
- ts.ScriptTarget.Latest,
28
- );
29
- const result = printer.printNode(ts.EmitHint.Unspecified, output[0], resultFile);
33
+ const resultFile = ts.createSourceFile(path, "", ts.ScriptTarget.Latest);
34
+ const result = output
35
+ .filter((node) => !isEmpty(node))
36
+ .map((node) => printer.printNode(ts.EmitHint.Unspecified, node, resultFile))
37
+ .join("\n\n");
30
38
  fs.writeFileSync(
31
- `src/__definitions__/${service.replace("https://", "")}-definitions.ts`,
39
+ path,
32
40
  (
33
41
  await format(
34
- `src/__definitions__/${service.replace("https://", "")}-definitions.ts`,
42
+ path,
35
43
  `// Autogenerated by two-stroke
36
44
 
37
45
  ${fs.readFileSync("src/__definitions__/type-definitions.ts", "utf8")}
package/bin/lint.mjs CHANGED
@@ -3,9 +3,13 @@
3
3
  import fs from "fs";
4
4
  import { cmd } from "../src/cmd.mjs";
5
5
 
6
+ const isApp = fs.readdirSync(".").some((file) => file.startsWith("vite.config."));
7
+
6
8
  if (fs.existsSync("wrangler.jsonc")) {
7
9
  cmd("wrangler types --env-file /dev/null --strict-vars false --check");
8
10
  }
9
- cmd("react-router typegen");
11
+ if (isApp) {
12
+ cmd("react-router typegen");
13
+ }
10
14
  cmd("oxfmt --check");
11
- cmd("oxlint --report-unused-disable-directives-severity=warn");
15
+ cmd("oxlint --report-unused-disable-directives-severity=error");
package/bin/test.mjs CHANGED
@@ -3,6 +3,7 @@
3
3
  import { cmd } from "../src/cmd.mjs";
4
4
 
5
5
  cmd("vitest", [
6
+ "--passWithNoTests",
6
7
  ...(!process.argv.slice(2).includes("-w") && !process.argv.slice(2).includes("--watch")
7
8
  ? ["--run"]
8
9
  : []),
@@ -3,9 +3,13 @@
3
3
  import fs from "fs";
4
4
  import { cmd } from "../src/cmd.mjs";
5
5
 
6
+ const isApp = fs.readdirSync(".").some((file) => file.startsWith("vite.config."));
7
+
6
8
  if (fs.existsSync("wrangler.jsonc")) {
7
9
  cmd("wrangler types --env-file /dev/null --strict-vars false");
8
10
  }
9
- cmd("react-router typegen");
10
- cmd("vite build");
11
+ if (isApp) {
12
+ cmd("react-router typegen");
13
+ cmd("vite build");
14
+ }
11
15
  cmd("tsc --noEmit");
package/oxlint.config.mjs CHANGED
@@ -1,17 +1,19 @@
1
1
  import { defineConfig } from "oxlint";
2
2
 
3
3
  export default defineConfig({
4
- plugins: ["eslint", "typescript", "unicorn", "oxc", "import", "promise", "vitest", "react"],
4
+ plugins: ["eslint", "typescript", "unicorn", "oxc", "import", "promise", "react"],
5
5
  options: {
6
6
  typeAware: true,
7
7
  },
8
8
  categories: {
9
9
  correctness: "error",
10
- suspicious: "warn",
10
+ suspicious: "error",
11
11
  perf: "error",
12
- style: "warn",
12
+ style: "error",
13
13
  },
14
14
  rules: {
15
+ // Judge a wrapped comment by its first line, so the second can carry on the sentence.
16
+ "capitalized-comments": ["error", "always", { ignoreConsecutiveComments: true }],
15
17
  "no-map-spread": "off",
16
18
  "no-named-export": "off",
17
19
  "no-magic-numbers": "off",
@@ -22,7 +24,6 @@ export default defineConfig({
22
24
  "sort-keys": "off",
23
25
  curly: "off",
24
26
  "switch-case-braces": "off",
25
- "vitest/require-hook": "off",
26
27
  "no-ternary": "off",
27
28
  "group-exports": "off",
28
29
  "exports-last": "off",
@@ -30,7 +31,6 @@ export default defineConfig({
30
31
  "no-null": "off",
31
32
  "consistent-type-specifier-style": "off",
32
33
  "max-statements": "off",
33
- "consistent-type-imports": "error",
34
34
  "relative-url-style": ["error", "always"],
35
35
  "no-namespace": "off",
36
36
  "no-duplicate-imports": ["error", { allowSeparateTypeImports: true }],
@@ -53,45 +53,87 @@ export default defineConfig({
53
53
  "no-anonymous-default-export": "off",
54
54
  "no-continue": "off",
55
55
  "init-declarations": "off",
56
- "vitest/prefer-to-be-truthy": "off",
57
- "vitest/prefer-to-be-falsy": "off",
58
- "vitest/prefer-expect-assertions": "off",
59
- "vitest/prefer-importing-vitest-globals": "off",
60
- "vitest/no-importing-vitest-globals": "error",
61
- "vitest/require-top-level-describe": "off",
62
- "vitest/max-expects": "off",
63
- "vitest/no-hooks": "off",
64
56
  "unicorn/number-literal-case": "off",
65
57
  "eslint/one-var": "off",
66
- "vitest/prefer-called-once": "off",
67
- // Judge a wrapped comment by its first line, so the second can carry on the sentence.
68
- "capitalized-comments": ["warn", "always", { ignoreConsecutiveComments: true }],
69
- // Assertion helpers are `assert` or named `expect…` (`expectRedirect`), so it
70
- // can see them; anything left over really asserts nothing.
71
- "vitest/expect-expect": ["warn", { assertFunctionNames: ["expect", "expect*", "assert"] }],
72
- // Route tests are titled by the request they make: "GET /thing", not "gET /thing".
73
- "vitest/prefer-lowercase-title": [
74
- "warn",
75
- { allowedPrefixes: ["GET", "POST", "PUT", "DELETE", "PATCH", "JWT"] },
76
- ],
77
- "react/react-in-jsx-scope": "off",
78
- "react/jsx-max-depth": "off",
58
+ // A ternary only reads better when both branches fit on one line.
59
+ "unicorn/prefer-ternary": ["error", "only-single-line"],
60
+ "unicorn/prefer-spread": "off", // Upstream bug
61
+ // Positional capture groups are used throughout; naming them all would
62
+ // reindex every `match[n]` consumer for no functional gain.
63
+ "eslint/prefer-named-capture-group": "off",
64
+ // Stylesheets can only be pulled in for their side effects.
65
+ "import/no-unassigned-import": ["error", { allow: ["**/*.css", "**/*.scss"] }],
66
+ // Bug-catching rules oxc files under pedantic or restriction, which are not enabled wholesale.
67
+ "typescript/no-misused-promises": "error",
68
+ "typescript/switch-exhaustiveness-check": "error",
69
+ "typescript/only-throw-error": "error",
70
+ "eslint/array-callback-return": "error",
71
+ "typescript/no-deprecated": "error",
72
+ "eslint/eqeqeq": "error",
73
+ "typescript/no-explicit-any": "error",
74
+ "typescript/no-dynamic-delete": "error",
75
+ "import/no-cycle": "error",
76
+ "eslint/no-empty": "error",
77
+ "typescript/use-unknown-in-catch-callback-variable": "error",
78
+ "react/jsx-max-depth": ["error", { max: 4 }],
79
+ "react/react-in-jsx-scope": "off", // Automatic JSX runtime (tsconfig `jsx: react-jsx`)
79
80
  "react/function-component-definition": [
80
81
  "error",
81
- {
82
- namedComponents: "arrow-function",
83
- unnamedComponents: "arrow-function",
84
- },
82
+ { namedComponents: "arrow-function", unnamedComponents: "arrow-function" },
85
83
  ],
86
84
  },
87
85
  overrides: [
88
86
  {
89
- files: ["*.test.ts", "*.spec.ts"],
87
+ // Mocks and page factories are test code too, and use vitest.
88
+ files: [
89
+ "*.test.ts",
90
+ "*.test.tsx",
91
+ "*.spec.ts",
92
+ "*.spec.tsx",
93
+ "e2e/**",
94
+ "**/__mocks__/**",
95
+ "**/__factories__/**",
96
+ ],
97
+ plugins: ["vitest"],
90
98
  rules: {
91
- "@typescript-eslint/no-explicit-any": "off",
99
+ "vitest/require-hook": "off",
100
+ "vitest/prefer-to-be-truthy": "off",
101
+ "vitest/prefer-to-be-falsy": "off",
102
+ "vitest/prefer-expect-assertions": "off",
103
+ "vitest/prefer-importing-vitest-globals": "off",
104
+ "vitest/prefer-describe-function-title": "off",
105
+ "vitest/require-top-level-describe": "off",
106
+ "vitest/max-expects": "off",
107
+ "vitest/no-hooks": "off",
108
+ "vitest/prefer-called-once": "off",
109
+ // `toEqual` ignores keys written as `undefined`; `toStrictEqual` does not. Fixtures
110
+ // spell those out on purpose, so the fix changes what a test asserts.
111
+ "vitest/prefer-strict-equal": "off",
112
+ // `vi.mock(import("x"))` does not type-check against vi.mock's overloads.
113
+ "vitest/prefer-import-in-mock": "off",
114
+ // A mock standing in for a whole module would restate that module's signatures.
115
+ "vitest/require-mock-type-parameters": "off",
116
+ // Assertion helpers are named `expect…` or `assert…` (`expectRedirect`), or are
117
+ // page-object methods that wrap expect(), so it can see them; anything left over
118
+ // really asserts nothing.
119
+ "vitest/expect-expect": [
120
+ "error",
121
+ { assertFunctionNames: ["expect*", "assert*", "page.expect*", "page.*Present"] },
122
+ ],
123
+ "vitest/prefer-lowercase-title": "off",
124
+ // Tests stash a method to restore it after stubbing. It is never called, so
125
+ // `this` never matters.
126
+ "unbound-method": "off",
92
127
  // A helper belongs next to the only test that uses it; hoisting it to module
93
128
  // scope saves nothing in a test run and puts it out of sight.
94
129
  "unicorn/consistent-function-scoping": "off",
130
+ "vitest/no-conditional-expect": "off",
131
+ "vitest/prefer-called-with": "off",
132
+ "prefer-arrow-callback": "off",
133
+ "vitest/consistent-vitest-vi": "off", // Upstream bug
134
+ "vitest/consistent-test-filename": "off",
135
+ // UI steps, polling and findBy queries are sequential by design.
136
+ "eslint/no-await-in-loop": "off",
95
137
  },
96
138
  },
97
139
  {
@@ -100,13 +142,10 @@ export default defineConfig({
100
142
  "no-unused-vars": "off",
101
143
  "@typescript-eslint/no-redundant-type-constituents": "off",
102
144
  "@typescript-eslint/no-duplicate-type-constituents": "off",
103
- "group-exports": "off",
104
- "exports-last": "off",
105
- "consistent-indexed-object-style": "off",
106
145
  },
107
146
  },
108
147
  {
109
- files: ["bin/*.mjs", "src/cmd.mjs"],
148
+ files: ["bin/*.mjs", "src/cmd.mjs", "src/openapi-typescript.mjs"],
110
149
  rules: {
111
150
  "import/no-nodejs-modules": "off",
112
151
  "prefer-destructuring": "off",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "four-stroke",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "description": "Simple Cloudflare Worker SSR SPA framework",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,15 +20,16 @@
20
20
  "main": "src/fake.ts",
21
21
  "dependencies": {
22
22
  "@types/react": "^19.3.0",
23
+ "@typescript/typescript6": "^6.0.2",
23
24
  "openapi-typescript": "^7.13.0",
24
25
  "typescript": "^7.0.2"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@cloudflare/vitest-pool-workers": "^0.22.0",
28
- "knip": "^6.35.1",
29
- "oxfmt": "^0.70.0",
30
- "oxlint": "^1.82.0",
31
- "oxlint-tsgolint": "^7.0.2001",
29
+ "knip": "^6.38.0",
30
+ "oxfmt": "^0.71.0",
31
+ "oxlint": "^1.86.0",
32
+ "oxlint-tsgolint": "^7.0.2003",
32
33
  "vitest": "^4.1.11"
33
34
  },
34
35
  "peerDependencies": {
@@ -43,5 +44,12 @@
43
44
  },
44
45
  "engines": {
45
46
  "node": "^24.9.0"
47
+ },
48
+ "scripts": {
49
+ "dev": "node bin/dev.mjs",
50
+ "format": "node bin/format.mjs",
51
+ "lint": "node bin/lint.mjs",
52
+ "test": "node bin/test.mjs",
53
+ "type-check": "node bin/type-check.mjs"
46
54
  }
47
55
  }
@@ -1,8 +1,6 @@
1
1
  minimumReleaseAge: 4320
2
- minimumReleaseAgeExclude:
3
- - "jwk-subtle"
4
- - "pbkdf-subtle"
5
- - "eslint-config-two-stroke"
6
2
  trustPolicy: no-downgrade
3
+ trustPolicyExclude:
4
+ - "why-is-node-running@3.2.2"
7
5
  ignoreScripts: true
8
6
  blockExoticSubdeps: true
@@ -0,0 +1,15 @@
1
+ import { registerHooks } from "node:module";
2
+
3
+ // Openapi-typescript needs the TypeScript 6 JS API (ts.factory), which TypeScript 7 no longer
4
+ // ships. Redirect its `typescript` import to @typescript/typescript6, resolved from two-stroke's
5
+ // own dependencies so this works however two-stroke is installed.
6
+ registerHooks({
7
+ resolve(specifier, context, nextResolve) {
8
+ if (specifier === "typescript" && context.parentURL?.includes("/openapi-typescript/")) {
9
+ return nextResolve("@typescript/typescript6", { ...context, parentURL: import.meta.url });
10
+ }
11
+ return nextResolve(specifier, context);
12
+ },
13
+ });
14
+
15
+ export default (await import("openapi-typescript")).default;