@askrjs/cli 0.0.3 → 0.0.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 +37 -0
- package/dist/add.d.ts +16 -0
- package/dist/add.js +33 -29
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +0 -2
- package/dist/create.d.ts +5 -0
- package/dist/create.js +104 -26
- package/dist/directory-swap-DWoHtx7C.js +35 -0
- package/dist/discovery-Difb7Y4G.js +0 -2
- package/dist/generate.d.ts +5 -0
- package/dist/generate.js +102 -27
- package/dist/is-direct-execution-Cdlr-ZUl.js +0 -2
- package/dist/openapi.d.ts +25 -0
- package/dist/openapi.js +44 -12
- package/dist/planner-VAj7qlxr.js +0 -2
- package/dist/range-YUs9eimn.js +0 -2
- package/dist/registry-CyrRHRG5.js +0 -2
- package/dist/skills/askr-ssr-ssg/SKILL.md +3 -1
- package/dist/{skills-Bh0mEhIx.js → skills-B7CbWur9.js} +89 -26
- package/dist/skills.d.ts +18 -0
- package/dist/skills.js +1 -1
- package/dist/specification-DXnDOC-0.js +0 -2
- package/dist/ssg-config.d.ts +41 -0
- package/dist/ssg-config.js +1 -0
- package/dist/ssg.d.ts +61 -0
- package/dist/ssg.js +360 -24
- package/dist/templates/full-stack/package.json +1 -1
- package/dist/templates/full-stack/src/server/action-registry.ts +0 -1
- package/dist/templates/full-stack/tsconfig.json +1 -1
- package/dist/templates/spa/package.json +2 -3
- package/dist/templates/spa/src/pages/app/_routes.tsx +2 -0
- package/dist/templates/spa/src/pages/public/_routes.tsx +2 -0
- package/dist/templates/spa/tsconfig.json +2 -1
- package/dist/templates/spa/vitest.config.ts +0 -4
- package/dist/templates/ssg/README.md +1 -1
- package/dist/templates/ssg/package.json +1 -1
- package/dist/templates/ssg/ssg.config.ts +7 -0
- package/dist/templates/ssg/tests/ssg-config.test.ts +2 -0
- package/dist/templates/ssg/tsconfig.json +2 -1
- package/dist/templates/ssg/vitest.config.ts +0 -4
- package/dist/templates/ssr/package.json +2 -2
- package/dist/templates/startkit/package.json +2 -2
- package/dist/templates/startkit/src/lib/mock-data.ts +17 -39
- package/dist/templates/startkit/tsconfig.json +2 -1
- package/dist/update.d.ts +41 -0
- package/dist/update.js +0 -2
- package/dist/writer-D8qe_7ud.js +0 -2
- package/package.json +13 -8
- package/dist/add.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/create.js.map +0 -1
- package/dist/discovery-Difb7Y4G.js.map +0 -1
- package/dist/generate.js.map +0 -1
- package/dist/is-direct-execution-Cdlr-ZUl.js.map +0 -1
- package/dist/openapi.js.map +0 -1
- package/dist/planner-VAj7qlxr.js.map +0 -1
- package/dist/range-YUs9eimn.js.map +0 -1
- package/dist/registry-CyrRHRG5.js.map +0 -1
- package/dist/skills-Bh0mEhIx.js.map +0 -1
- package/dist/specification-DXnDOC-0.js.map +0 -1
- package/dist/ssg.js.map +0 -1
- package/dist/templates/full-stack/src/vite-server.d.ts +0 -5
- package/dist/templates/spa/src/styles.d.ts +0 -1
- package/dist/templates/ssg/src/vite-env.d.ts +0 -5
- package/dist/templates/ssr/src/vite-server.d.ts +0 -7
- package/dist/templates/startkit/src/vite-env.d.ts +0 -5
- package/dist/update.js.map +0 -1
- package/dist/writer-D8qe_7ud.js.map +0 -1
- package/skills/askr-accessibility/SKILL.md +0 -68
- package/skills/askr-accessibility/agents/openai.yaml +0 -4
- package/skills/askr-agent-execution/SKILL.md +0 -85
- package/skills/askr-agent-execution/agents/openai.yaml +0 -4
- package/skills/askr-agent-workflows/SKILL.md +0 -57
- package/skills/askr-agent-workflows/agents/openai.yaml +0 -4
- package/skills/askr-api-integration/SKILL.md +0 -79
- package/skills/askr-api-integration/agents/openai.yaml +0 -4
- package/skills/askr-app-builder/SKILL.md +0 -99
- package/skills/askr-app-builder/agents/openai.yaml +0 -4
- package/skills/askr-auth-access/SKILL.md +0 -74
- package/skills/askr-auth-access/agents/openai.yaml +0 -4
- package/skills/askr-cli-vite/SKILL.md +0 -76
- package/skills/askr-cli-vite/agents/openai.yaml +0 -4
- package/skills/askr-dashboard-charts/SKILL.md +0 -99
- package/skills/askr-dashboard-charts/agents/openai.yaml +0 -4
- package/skills/askr-design-system/SKILL.md +0 -86
- package/skills/askr-design-system/agents/openai.yaml +0 -4
- package/skills/askr-env-config/SKILL.md +0 -74
- package/skills/askr-env-config/agents/openai.yaml +0 -4
- package/skills/askr-error-loading-empty/SKILL.md +0 -89
- package/skills/askr-error-loading-empty/agents/openai.yaml +0 -4
- package/skills/askr-file-upload-artifacts/SKILL.md +0 -77
- package/skills/askr-file-upload-artifacts/agents/openai.yaml +0 -4
- package/skills/askr-forms-tables-crud/SKILL.md +0 -98
- package/skills/askr-forms-tables-crud/agents/openai.yaml +0 -4
- package/skills/askr-mental-model/SKILL.md +0 -109
- package/skills/askr-mental-model/agents/openai.yaml +0 -4
- package/skills/askr-migration-react/SKILL.md +0 -81
- package/skills/askr-migration-react/agents/openai.yaml +0 -4
- package/skills/askr-observability-debugging/SKILL.md +0 -83
- package/skills/askr-observability-debugging/agents/openai.yaml +0 -4
- package/skills/askr-project-structure/SKILL.md +0 -79
- package/skills/askr-project-structure/agents/openai.yaml +0 -4
- package/skills/askr-query-mutation/SKILL.md +0 -115
- package/skills/askr-query-mutation/agents/openai.yaml +0 -4
- package/skills/askr-realtime-streaming/SKILL.md +0 -76
- package/skills/askr-realtime-streaming/agents/openai.yaml +0 -4
- package/skills/askr-resources-data/SKILL.md +0 -100
- package/skills/askr-resources-data/agents/openai.yaml +0 -4
- package/skills/askr-routing-layouts/SKILL.md +0 -107
- package/skills/askr-routing-layouts/agents/openai.yaml +0 -4
- package/skills/askr-runtime-reactivity/SKILL.md +0 -92
- package/skills/askr-runtime-reactivity/agents/openai.yaml +0 -4
- package/skills/askr-ssr-ssg/SKILL.md +0 -76
- package/skills/askr-ssr-ssg/agents/openai.yaml +0 -4
- package/skills/askr-testing-determinism/SKILL.md +0 -75
- package/skills/askr-testing-determinism/agents/openai.yaml +0 -4
- package/skills/askr-theming/SKILL.md +0 -86
- package/skills/askr-theming/agents/openai.yaml +0 -4
- package/skills/askr-ui-composition/SKILL.md +0 -92
- package/skills/askr-ui-composition/agents/openai.yaml +0 -4
- package/templates/full-stack/AGENTS.md +0 -11
- package/templates/full-stack/README.md +0 -17
- package/templates/full-stack/gitignore.template +0 -4
- package/templates/full-stack/index.html +0 -13
- package/templates/full-stack/package.json +0 -39
- package/templates/full-stack/server.ts +0 -9
- package/templates/full-stack/src/action-authorizations.ts +0 -10
- package/templates/full-stack/src/actions/create-message.ts +0 -10
- package/templates/full-stack/src/i18n.ts +0 -8
- package/templates/full-stack/src/main.tsx +0 -11
- package/templates/full-stack/src/pages/home.tsx +0 -25
- package/templates/full-stack/src/pages/layout.tsx +0 -43
- package/templates/full-stack/src/pages/not-found.tsx +0 -7
- package/templates/full-stack/src/routes.tsx +0 -19
- package/templates/full-stack/src/schemas.ts +0 -10
- package/templates/full-stack/src/server/action-registry.ts +0 -8
- package/templates/full-stack/src/server/actions/create-message.ts +0 -14
- package/templates/full-stack/src/server/app.ts +0 -76
- package/templates/full-stack/src/server/dependencies.ts +0 -55
- package/templates/full-stack/src/server/entry-server.ts +0 -8
- package/templates/full-stack/src/telemetry.ts +0 -3
- package/templates/full-stack/src/vite-server.d.ts +0 -5
- package/templates/full-stack/tests/actions/create-message.test.ts +0 -12
- package/templates/full-stack/tsconfig.json +0 -15
- package/templates/full-stack/vite.config.ts +0 -10
- package/templates/spa/AGENTS.md +0 -60
- package/templates/spa/README.md +0 -96
- package/templates/spa/gitignore.template +0 -16
- package/templates/spa/index.html +0 -13
- package/templates/spa/package.json +0 -35
- package/templates/spa/src/adapters/operations-client.ts +0 -95
- package/templates/spa/src/components/shared/metric-card.tsx +0 -27
- package/templates/spa/src/components/shared/status-badge.tsx +0 -24
- package/templates/spa/src/features/operations/operations.query.ts +0 -9
- package/templates/spa/src/main.tsx +0 -10
- package/templates/spa/src/pages/_layout.tsx +0 -9
- package/templates/spa/src/pages/_routes.tsx +0 -27
- package/templates/spa/src/pages/app/_layout.tsx +0 -94
- package/templates/spa/src/pages/app/_routes.tsx +0 -10
- package/templates/spa/src/pages/app/admin-home.tsx +0 -170
- package/templates/spa/src/pages/app/agent-runs.tsx +0 -93
- package/templates/spa/src/pages/app/settings.tsx +0 -91
- package/templates/spa/src/pages/auth/_layout.tsx +0 -38
- package/templates/spa/src/pages/auth/_routes.tsx +0 -6
- package/templates/spa/src/pages/auth/login.tsx +0 -63
- package/templates/spa/src/pages/not-found.tsx +0 -22
- package/templates/spa/src/pages/public/_layout.tsx +0 -47
- package/templates/spa/src/pages/public/_routes.tsx +0 -6
- package/templates/spa/src/pages/public/home.tsx +0 -143
- package/templates/spa/src/shared/format.ts +0 -12
- package/templates/spa/src/shared/navigation.ts +0 -15
- package/templates/spa/src/styles/components.css +0 -49
- package/templates/spa/src/styles/layout.css +0 -192
- package/templates/spa/src/styles/reset.css +0 -44
- package/templates/spa/src/styles/theme.css +0 -57
- package/templates/spa/src/styles/tokens.css +0 -20
- package/templates/spa/src/styles.css +0 -7
- package/templates/spa/src/styles.d.ts +0 -1
- package/templates/spa/tests/app.test.tsx +0 -37
- package/templates/spa/tests/components/shared.test.tsx +0 -12
- package/templates/spa/tests/resources.test.ts +0 -22
- package/templates/spa/tsconfig.json +0 -20
- package/templates/spa/tsconfig.node.json +0 -10
- package/templates/spa/vite.config.ts +0 -24
- package/templates/spa/vitest.config.ts +0 -17
- package/templates/ssg/AGENTS.md +0 -55
- package/templates/ssg/README.md +0 -39
- package/templates/ssg/gitignore.template +0 -17
- package/templates/ssg/index.html +0 -13
- package/templates/ssg/package.json +0 -35
- package/templates/ssg/src/app.tsx +0 -14
- package/templates/ssg/src/components/badge.tsx +0 -3
- package/templates/ssg/src/components/counter.tsx +0 -30
- package/templates/ssg/src/components/site-shell.tsx +0 -111
- package/templates/ssg/src/jsx.d.ts +0 -23
- package/templates/ssg/src/main.tsx +0 -8
- package/templates/ssg/src/pages/about.tsx +0 -82
- package/templates/ssg/src/pages/content.tsx +0 -63
- package/templates/ssg/src/pages/example.tsx +0 -97
- package/templates/ssg/src/pages/home.tsx +0 -51
- package/templates/ssg/src/resources/user.ts +0 -15
- package/templates/ssg/src/routes.tsx +0 -15
- package/templates/ssg/src/styles.css +0 -89
- package/templates/ssg/src/vite-env.d.ts +0 -5
- package/templates/ssg/ssg.config.ts +0 -34
- package/templates/ssg/tests/app.test.tsx +0 -19
- package/templates/ssg/tests/components/counter.test.tsx +0 -18
- package/templates/ssg/tests/resources.test.ts +0 -11
- package/templates/ssg/tests/ssg-config.test.ts +0 -11
- package/templates/ssg/tsconfig.json +0 -20
- package/templates/ssg/tsconfig.node.json +0 -10
- package/templates/ssg/vite.config.ts +0 -24
- package/templates/ssg/vitest.config.ts +0 -17
- package/templates/ssr/AGENTS.md +0 -28
- package/templates/ssr/README.md +0 -38
- package/templates/ssr/gitignore.template +0 -16
- package/templates/ssr/index.html +0 -13
- package/templates/ssr/package.json +0 -34
- package/templates/ssr/server.ts +0 -10
- package/templates/ssr/src/app.tsx +0 -21
- package/templates/ssr/src/components/counter.tsx +0 -26
- package/templates/ssr/src/entry-server.tsx +0 -12
- package/templates/ssr/src/main.tsx +0 -11
- package/templates/ssr/src/pages/about.tsx +0 -75
- package/templates/ssr/src/pages/example.tsx +0 -113
- package/templates/ssr/src/pages/home.tsx +0 -56
- package/templates/ssr/src/resources/user.ts +0 -15
- package/templates/ssr/src/routes.tsx +0 -13
- package/templates/ssr/src/styles.css +0 -148
- package/templates/ssr/src/vite-server.d.ts +0 -7
- package/templates/ssr/tests/app.test.tsx +0 -24
- package/templates/ssr/tests/components/counter.test.tsx +0 -13
- package/templates/ssr/tests/resources.test.ts +0 -11
- package/templates/ssr/tsconfig.json +0 -22
- package/templates/ssr/tsconfig.node.json +0 -10
- package/templates/ssr/tsconfig.server.json +0 -15
- package/templates/ssr/vite.config.ts +0 -25
- package/templates/ssr/vitest.config.ts +0 -13
- package/templates/startkit/AGENTS.md +0 -77
- package/templates/startkit/README.md +0 -192
- package/templates/startkit/gitignore.template +0 -16
- package/templates/startkit/index.html +0 -13
- package/templates/startkit/package.json +0 -35
- package/templates/startkit/src/components/app-header.tsx +0 -93
- package/templates/startkit/src/components/app-sidebar.tsx +0 -95
- package/templates/startkit/src/components/data-table.tsx +0 -80
- package/templates/startkit/src/components/empty-state.tsx +0 -13
- package/templates/startkit/src/components/page-header.tsx +0 -23
- package/templates/startkit/src/components/stat-card.tsx +0 -24
- package/templates/startkit/src/features/accounts/account-filters.tsx +0 -61
- package/templates/startkit/src/features/accounts/account-table.tsx +0 -79
- package/templates/startkit/src/lib/format.ts +0 -31
- package/templates/startkit/src/lib/mock-data.ts +0 -441
- package/templates/startkit/src/lib/routes.ts +0 -116
- package/templates/startkit/src/main.tsx +0 -12
- package/templates/startkit/src/pages/_layout.tsx +0 -56
- package/templates/startkit/src/pages/auth/_layout.tsx +0 -7
- package/templates/startkit/src/pages/auth/login.tsx +0 -129
- package/templates/startkit/src/pages/home.tsx +0 -83
- package/templates/startkit/src/pages/not-found.tsx +0 -24
- package/templates/startkit/src/pages/workspace/_layout.tsx +0 -17
- package/templates/startkit/src/pages/workspace/accounts/index.tsx +0 -217
- package/templates/startkit/src/pages/workspace/dashboard.tsx +0 -118
- package/templates/startkit/src/pages/workspace/settings.tsx +0 -218
- package/templates/startkit/src/router.tsx +0 -7
- package/templates/startkit/src/routes/auth-config.ts +0 -38
- package/templates/startkit/src/routes/auth.ts +0 -8
- package/templates/startkit/src/routes/index.ts +0 -28
- package/templates/startkit/src/routes/public.ts +0 -8
- package/templates/startkit/src/routes/workspace/accounts.ts +0 -8
- package/templates/startkit/src/routes/workspace/index.ts +0 -14
- package/templates/startkit/src/styles/components.css +0 -396
- package/templates/startkit/src/styles/layout.css +0 -192
- package/templates/startkit/src/styles/reset.css +0 -38
- package/templates/startkit/src/styles/theme.css +0 -76
- package/templates/startkit/src/styles/tokens.css +0 -67
- package/templates/startkit/src/styles.css +0 -7
- package/templates/startkit/src/toast.ts +0 -55
- package/templates/startkit/src/utils/join-classes.ts +0 -4
- package/templates/startkit/src/vite-env.d.ts +0 -5
- package/templates/startkit/tests/app.test.tsx +0 -32
- package/templates/startkit/tests/preferences.test.ts +0 -43
- package/templates/startkit/tests/resources.test.ts +0 -82
- package/templates/startkit/tsconfig.json +0 -20
- package/templates/startkit/tsconfig.node.json +0 -10
- package/templates/startkit/vite.config.ts +0 -32
- package/templates/startkit/vitest.config.ts +0 -13
package/dist/generate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
1
|
+
import { mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
4
|
import { load } from "js-yaml";
|
|
5
5
|
//#region src/generate/generator.ts
|
|
@@ -29,25 +29,38 @@ const pascal = (name) => {
|
|
|
29
29
|
const result = name.replace(/(^|[^A-Za-z0-9]+)([A-Za-z0-9])/g, (_, _s, c) => c.toUpperCase()).replace(/[^A-Za-z0-9_$]/g, "");
|
|
30
30
|
return /^\d/.test(result) ? `_${result}` : result;
|
|
31
31
|
};
|
|
32
|
-
function schemaType(schema, at, direction = "response") {
|
|
32
|
+
function schemaType(schema, at, direction = "response", references) {
|
|
33
33
|
if (schema.$ref) {
|
|
34
34
|
const name = decodeURIComponent(String(schema.$ref).split("/").at(-1));
|
|
35
|
-
|
|
35
|
+
const typeName = pascal(name);
|
|
36
|
+
references?.add(typeName);
|
|
37
|
+
return typeName;
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(schema.type)) {
|
|
40
|
+
const includesNull = schema.type.includes("null");
|
|
41
|
+
const rendered = [...new Set(schema.type.filter((value) => value !== "null"))].map((variant) => schemaType({
|
|
42
|
+
...schema,
|
|
43
|
+
type: variant,
|
|
44
|
+
nullable: false
|
|
45
|
+
}, at, direction, references));
|
|
46
|
+
if (includesNull) rendered.push("null");
|
|
47
|
+
if (rendered.length === 0) fail("Schema type array must not be empty", at);
|
|
48
|
+
return [...new Set(rendered)].join(" | ");
|
|
36
49
|
}
|
|
37
50
|
if (schema.const !== void 0) return JSON.stringify(schema.const);
|
|
38
51
|
if (schema.enum) return schema.enum.map((value) => JSON.stringify(value)).join(" | ") || "never";
|
|
39
|
-
if (schema.oneOf || schema.anyOf) return (schema.oneOf ?? schema.anyOf).map((item, i) => schemaType(item, `${at}/${schema.oneOf ? "oneOf" : "anyOf"}/${i}`, direction)).join(" | ");
|
|
40
|
-
if (schema.allOf) return schema.allOf.map((item, i) => schemaType(item, `${at}/allOf/${i}`, direction)).join(" & ");
|
|
52
|
+
if (schema.oneOf || schema.anyOf) return (schema.oneOf ?? schema.anyOf).map((item, i) => schemaType(item, `${at}/${schema.oneOf ? "oneOf" : "anyOf"}/${i}`, direction, references)).join(" | ");
|
|
53
|
+
if (schema.allOf) return schema.allOf.map((item, i) => schemaType(item, `${at}/allOf/${i}`, direction, references)).join(" & ");
|
|
41
54
|
let type = "never";
|
|
42
55
|
if (schema.type === "string") type = "string";
|
|
43
56
|
else if (schema.type === "number" || schema.type === "integer") type = "number";
|
|
44
57
|
else if (schema.type === "boolean") type = "boolean";
|
|
45
58
|
else if (schema.type === "null") type = "null";
|
|
46
|
-
else if (schema.type === "array" || schema.items) type = `Array<${schemaType(schema.items ?? {}, `${at}/items`, direction)}>`;
|
|
59
|
+
else if (schema.type === "array" || schema.items) type = `Array<${schemaType(schema.items ?? {}, `${at}/items`, direction, references)}>`;
|
|
47
60
|
else if (schema.type === "object" || schema.properties || schema.additionalProperties !== void 0) {
|
|
48
61
|
const required = new Set(schema.required ?? []);
|
|
49
|
-
const properties = Object.entries(schema.properties ?? {}).filter(([, value]) => !(direction === "request" ? value.readOnly : value.writeOnly)).map(([key, value]) => ` ${JSON.stringify(key)}${required.has(key) ? "" : "?"}: ${schemaType(value, `${at}/properties/${key}`, direction)};`);
|
|
50
|
-
if (schema.additionalProperties && typeof schema.additionalProperties === "object") properties.push(` [key: string]: ${schemaType(schema.additionalProperties, `${at}/additionalProperties`, direction)};`);
|
|
62
|
+
const properties = Object.entries(schema.properties ?? {}).filter(([, value]) => !(direction === "request" ? value.readOnly : value.writeOnly)).map(([key, value]) => ` ${JSON.stringify(key)}${required.has(key) ? "" : "?"}: ${schemaType(value, `${at}/properties/${key}`, direction, references)};`);
|
|
63
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === "object") properties.push(` [key: string]: ${schemaType(schema.additionalProperties, `${at}/additionalProperties`, direction, references)};`);
|
|
51
64
|
type = `{\n${properties.join("\n")}\n}`;
|
|
52
65
|
} else if (!schema.type) fail("Unsupported schema without a type", at);
|
|
53
66
|
else fail(`Unsupported schema type ${schema.type}`, at);
|
|
@@ -56,7 +69,7 @@ function schemaType(schema, at, direction = "response") {
|
|
|
56
69
|
function responseSchema(operation, at, success) {
|
|
57
70
|
return Object.entries(operation.responses ?? {}).filter(([status]) => status === "default" ? !success : success === /^2\d\d$/.test(status)).sort(([a], [b]) => a.localeCompare(b, void 0, { numeric: true }));
|
|
58
71
|
}
|
|
59
|
-
function pickContent(content, at, direction) {
|
|
72
|
+
function pickContent(content, at, direction, references) {
|
|
60
73
|
const entries = Object.entries(content ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
61
74
|
if (!entries.length) return {
|
|
62
75
|
type: "undefined",
|
|
@@ -65,7 +78,7 @@ function pickContent(content, at, direction) {
|
|
|
65
78
|
};
|
|
66
79
|
const types = entries.map(([mediaType, value]) => ({
|
|
67
80
|
mediaType,
|
|
68
|
-
type: schemaType(value.schema ?? {}, `${at}/content/${mediaType.replace(/~/g, "~0").replace(/\//g, "~1")}/schema`, direction)
|
|
81
|
+
type: schemaType(value.schema ?? {}, `${at}/content/${mediaType.replace(/~/g, "~0").replace(/\//g, "~1")}/schema`, direction, references)
|
|
69
82
|
}));
|
|
70
83
|
const union = [...new Set(types.map((v) => v.type))].join(" | ");
|
|
71
84
|
return {
|
|
@@ -218,6 +231,13 @@ function generateFiles(document) {
|
|
|
218
231
|
names.set(safe, name);
|
|
219
232
|
}
|
|
220
233
|
const schemaLines = [...names].map(([safe, original]) => `export type ${safe} = ${schemaType(schemas[original], pointer("components", "schemas", original))};`);
|
|
234
|
+
const operationSchemaReferences = /* @__PURE__ */ new Set();
|
|
235
|
+
const apiSchemaReferences = /* @__PURE__ */ new Set();
|
|
236
|
+
const apiOperationReferences = /* @__PURE__ */ new Set();
|
|
237
|
+
const contentSchemaReferences = { add(name) {
|
|
238
|
+
operationSchemaReferences.add(name);
|
|
239
|
+
apiSchemaReferences.add(name);
|
|
240
|
+
} };
|
|
221
241
|
const operationTypes = [];
|
|
222
242
|
const descriptors = [];
|
|
223
243
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -230,24 +250,38 @@ function generateFiles(document) {
|
|
|
230
250
|
if (seen.has(id)) fail("Duplicate operationId", `${at}/operationId`, id);
|
|
231
251
|
seen.add(id);
|
|
232
252
|
if (operation.callbacks) fail("Callbacks are unsupported", `${at}/callbacks`, id);
|
|
233
|
-
const
|
|
253
|
+
const parametersByIdentity = /* @__PURE__ */ new Map();
|
|
254
|
+
for (const parameter of [...document.paths[path].parameters ?? [], ...operation.parameters ?? []]) {
|
|
255
|
+
if (parameter.$ref) fail("Parameter references must be bundled", `${at}/parameters`, id);
|
|
256
|
+
if (typeof parameter.name !== "string" || typeof parameter.in !== "string") fail("Parameters require string name and in fields", `${at}/parameters`, id);
|
|
257
|
+
parametersByIdentity.set(`${parameter.in}\0${parameter.name}`, parameter);
|
|
258
|
+
}
|
|
259
|
+
const parameters = [...parametersByIdentity.values()];
|
|
234
260
|
const byLocation = {
|
|
235
261
|
path: [],
|
|
236
262
|
query: [],
|
|
237
263
|
header: []
|
|
238
264
|
};
|
|
239
265
|
for (const parameter of parameters) {
|
|
240
|
-
if (parameter.$ref) fail("Parameter references must be bundled", `${at}/parameters`, id);
|
|
241
266
|
if (parameter.in === "cookie") fail("Cookie parameters are unsupported", `${at}/parameters`, id);
|
|
242
267
|
if (!byLocation[parameter.in]) fail(`Unsupported parameter location ${parameter.in}`, `${at}/parameters`, id);
|
|
243
268
|
byLocation[parameter.in].push(parameter);
|
|
244
269
|
}
|
|
270
|
+
const templateParameters = new Set([...path.matchAll(/\{([^{}]+)\}/g)].map((match) => match[1]));
|
|
271
|
+
const declaredPathParameters = new Set(byLocation.path.map((parameter) => parameter.name));
|
|
272
|
+
for (const name of templateParameters) {
|
|
273
|
+
const parameter = byLocation.path.find((candidate) => candidate.name === name);
|
|
274
|
+
if (!parameter) fail(`Path template parameter ${name} is not declared`, `${at}/parameters`, id);
|
|
275
|
+
if (!parameter || parameter.required !== true) fail(`Path parameter ${name} must be required`, `${at}/parameters`, id);
|
|
276
|
+
}
|
|
277
|
+
for (const name of declaredPathParameters) if (!templateParameters.has(name)) fail(`Path parameter ${name} is not present in the path template`, `${at}/parameters`, id);
|
|
245
278
|
const prefix = pascal(id);
|
|
246
279
|
const renderParameters = (location) => {
|
|
247
280
|
const list = byLocation[location].sort((a, b) => a.name.localeCompare(b.name));
|
|
248
281
|
if (!list.length) return void 0;
|
|
249
282
|
const name = `${prefix}${pascal(location)}`;
|
|
250
|
-
|
|
283
|
+
apiOperationReferences.add(name);
|
|
284
|
+
operationTypes.push(`export type ${name} = {\n${list.map((p) => ` ${JSON.stringify(p.name)}${p.required || location === "path" ? "" : "?"}: ${schemaType(p.schema ?? {}, `${at}/parameters`, "request", operationSchemaReferences)};`).join("\n")}\n};`);
|
|
251
285
|
const defaultStyle = location === "query" ? "form" : "simple";
|
|
252
286
|
return {
|
|
253
287
|
name,
|
|
@@ -259,7 +293,7 @@ function generateFiles(document) {
|
|
|
259
293
|
};
|
|
260
294
|
};
|
|
261
295
|
const params = renderParameters("path"), query = renderParameters("query"), headers = renderParameters("header");
|
|
262
|
-
const body = operation.requestBody ? pickContent(operation.requestBody.content, `${at}/requestBody`, "request") : void 0;
|
|
296
|
+
const body = operation.requestBody ? pickContent(operation.requestBody.content, `${at}/requestBody`, "request", contentSchemaReferences) : void 0;
|
|
263
297
|
if (body) operationTypes.push(`export type ${prefix}Body = ${body.type};`);
|
|
264
298
|
const successes = responseSchema(operation, at, true);
|
|
265
299
|
if (!successes.length) fail("At least one 2xx response is required", `${at}/responses`, id);
|
|
@@ -271,7 +305,7 @@ function generateFiles(document) {
|
|
|
271
305
|
if (body) chain.push(`body(${body.codec})`);
|
|
272
306
|
for (const [status, response] of successes) {
|
|
273
307
|
if (response.links) fail("Response links are unsupported", `${at}/responses/${status}/links`, id);
|
|
274
|
-
const value = pickContent(response.content, `${at}/responses/${status}`, "response");
|
|
308
|
+
const value = pickContent(response.content, `${at}/responses/${status}`, "response", contentSchemaReferences);
|
|
275
309
|
const typeName = `${prefix}Response${status}`;
|
|
276
310
|
operationTypes.push(`export type ${typeName} = ${value.type};`);
|
|
277
311
|
chain.push(`returns(${status === "200" ? "" : `${status}, `}${value.codec})`);
|
|
@@ -279,7 +313,7 @@ function generateFiles(document) {
|
|
|
279
313
|
if (errors.length) {
|
|
280
314
|
const rendered = errors.map(([status, response]) => {
|
|
281
315
|
if (response.links) fail("Response links are unsupported", `${at}/responses/${status}/links`, id);
|
|
282
|
-
const value = pickContent(response.content, `${at}/responses/${status}`, "response");
|
|
316
|
+
const value = pickContent(response.content, `${at}/responses/${status}`, "response", contentSchemaReferences);
|
|
283
317
|
const typeName = `${prefix}Error${pascal(status)}`;
|
|
284
318
|
operationTypes.push(`export type ${typeName} = ${value.type};`);
|
|
285
319
|
return `${JSON.stringify(status)}: ${value.codec}`;
|
|
@@ -292,12 +326,12 @@ function generateFiles(document) {
|
|
|
292
326
|
const imports = [...new Set(descriptors.join(" ").match(/\b(get|post|put|patch|del|head|options|json|text|urlEncoded|multipart|arrayBuffer|empty|content)\b/g) ?? [])].sort();
|
|
293
327
|
const files = {
|
|
294
328
|
"schemas.ts": `${schemaLines.join("\n\n")}\n`,
|
|
295
|
-
"operations.ts": `${
|
|
329
|
+
"operations.ts": `${operationSchemaReferences.size ? `import type { ${[...operationSchemaReferences].sort().join(", ")} } from "./schemas";\n\n` : ""}${operationTypes.join("\n\n")}\n`,
|
|
296
330
|
"api.ts": `import { ${[
|
|
297
331
|
"defineApi",
|
|
298
332
|
"createClient",
|
|
299
333
|
...imports
|
|
300
|
-
].join(", ")} } from "@askrjs/fetch";\nimport type { ClientOptions } from "@askrjs/fetch";\
|
|
334
|
+
].join(", ")} } from "@askrjs/fetch";\nimport type { ClientOptions } from "@askrjs/fetch";\n${apiSchemaReferences.size ? `import type { ${[...apiSchemaReferences].sort().join(", ")} } from "./schemas";\n` : ""}${apiOperationReferences.size ? `import type { ${[...apiOperationReferences].sort().join(", ")} } from "./operations";\n` : ""}\nexport const api = defineApi({\n${descriptors.join("\n")}\n}, ${JSON.stringify({
|
|
301
335
|
servers: (document.servers ?? []).map((s) => s.url),
|
|
302
336
|
securitySchemes: document.components?.securitySchemes ?? {}
|
|
303
337
|
}, null, 2)});\n\nexport const createApiClient = (options?: ClientOptions) => createClient(api, options);\n`,
|
|
@@ -319,6 +353,8 @@ async function existingFiles(directory) {
|
|
|
319
353
|
}
|
|
320
354
|
async function writeGenerated(directory, files, check) {
|
|
321
355
|
const output = resolve(directory);
|
|
356
|
+
const outputStat = await stat(output).catch(() => null);
|
|
357
|
+
if (outputStat && !outputStat.isDirectory()) throw new GenerationError(`Generated output must be a directory: ${output}`);
|
|
322
358
|
const entries = await existingFiles(output);
|
|
323
359
|
if (check) {
|
|
324
360
|
if (!entries.length) throw new GenerationError(`Generated directory is missing: ${output}`);
|
|
@@ -357,6 +393,11 @@ async function writeGenerated(directory, files, check) {
|
|
|
357
393
|
}
|
|
358
394
|
}
|
|
359
395
|
async function generate(input, output, check = false) {
|
|
396
|
+
if (!/^https?:\/\//.test(input)) {
|
|
397
|
+
const resolvedInput = resolve(input);
|
|
398
|
+
const inputWithinOutput = relative(resolve(output), resolvedInput);
|
|
399
|
+
if (!inputWithinOutput.startsWith("..") && !isAbsolute(inputWithinOutput)) throw new GenerationError("Generated output must not contain its OpenAPI input document");
|
|
400
|
+
}
|
|
360
401
|
const files = generateFiles(await loadOpenApi(input));
|
|
361
402
|
await mkdir(dirname(resolve(output)), { recursive: true });
|
|
362
403
|
await writeGenerated(output, files, check);
|
|
@@ -364,33 +405,67 @@ async function generate(input, output, check = false) {
|
|
|
364
405
|
//#endregion
|
|
365
406
|
//#region src/bin/generate.ts
|
|
366
407
|
async function runGenerateCli(args, io = console) {
|
|
408
|
+
const wantsJson = args.includes("--json");
|
|
367
409
|
let output;
|
|
368
410
|
let check = false;
|
|
411
|
+
let json = false;
|
|
412
|
+
let help = false;
|
|
369
413
|
const inputs = [];
|
|
370
414
|
for (let i = 0; i < args.length; i++) {
|
|
371
415
|
const arg = args[i];
|
|
372
416
|
if (arg === "--check") check = true;
|
|
373
|
-
else if (arg === "
|
|
374
|
-
else if (arg
|
|
417
|
+
else if (arg === "--json") json = true;
|
|
418
|
+
else if (arg === "--help" || arg === "-h") help = true;
|
|
419
|
+
else if (arg === "-o" || arg === "--output") {
|
|
420
|
+
const value = args[i + 1];
|
|
421
|
+
if (!value || value.startsWith("-")) {
|
|
422
|
+
const message = `${arg} requires a path`;
|
|
423
|
+
io.error(wantsJson ? JSON.stringify({
|
|
424
|
+
status: "error",
|
|
425
|
+
error: message
|
|
426
|
+
}) : message);
|
|
427
|
+
return 1;
|
|
428
|
+
}
|
|
429
|
+
output = value;
|
|
430
|
+
i += 1;
|
|
431
|
+
} else if (arg.startsWith("--output=")) output = arg.slice(9);
|
|
375
432
|
else if (arg.startsWith("-")) {
|
|
376
|
-
|
|
433
|
+
const message = `Unknown option: ${arg}`;
|
|
434
|
+
io.error(wantsJson ? JSON.stringify({
|
|
435
|
+
status: "error",
|
|
436
|
+
error: message
|
|
437
|
+
}) : message);
|
|
377
438
|
return 1;
|
|
378
439
|
} else inputs.push(arg);
|
|
379
440
|
}
|
|
441
|
+
const usage = "Usage: askr generate <input> -o <output-directory> [--check] [--json]";
|
|
442
|
+
if (help) {
|
|
443
|
+
io.log(usage);
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
380
446
|
if (inputs.length !== 1 || !output) {
|
|
381
|
-
io.error(
|
|
447
|
+
io.error(json ? JSON.stringify({
|
|
448
|
+
status: "error",
|
|
449
|
+
error: usage
|
|
450
|
+
}) : usage);
|
|
382
451
|
return 1;
|
|
383
452
|
}
|
|
384
453
|
try {
|
|
385
454
|
await generate(inputs[0], output, check);
|
|
386
|
-
io.log(
|
|
455
|
+
io.log(json ? JSON.stringify({
|
|
456
|
+
status: "ok",
|
|
457
|
+
action: check ? "checked" : "generated",
|
|
458
|
+
output
|
|
459
|
+
}) : check ? "Generated client is current." : `Generated client in ${output}.`);
|
|
387
460
|
return 0;
|
|
388
461
|
} catch (error) {
|
|
389
|
-
|
|
462
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
463
|
+
io.error(json ? JSON.stringify({
|
|
464
|
+
status: "error",
|
|
465
|
+
error: message
|
|
466
|
+
}) : message);
|
|
390
467
|
return 1;
|
|
391
468
|
}
|
|
392
469
|
}
|
|
393
470
|
//#endregion
|
|
394
471
|
export { runGenerateCli };
|
|
395
|
-
|
|
396
|
-
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/bin/openapi.d.ts
|
|
2
|
+
type CliIo = Pick<Console, "error" | "log">;
|
|
3
|
+
interface ParsedArgs {
|
|
4
|
+
entry: string;
|
|
5
|
+
output: string;
|
|
6
|
+
check: boolean;
|
|
7
|
+
help: boolean;
|
|
8
|
+
json: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface OpenApiDeps {
|
|
11
|
+
cwd(): string;
|
|
12
|
+
importModule(filePath: string): Promise<unknown>;
|
|
13
|
+
readFile(filePath: string): Promise<string>;
|
|
14
|
+
mkdir(directory: string): Promise<void>;
|
|
15
|
+
writeFile(filePath: string, contents: string): Promise<void>;
|
|
16
|
+
rename(from: string, to: string): Promise<void>;
|
|
17
|
+
rm(filePath: string): Promise<void>;
|
|
18
|
+
temporarySuffix(): string;
|
|
19
|
+
}
|
|
20
|
+
declare function parseOpenApiArgs(args: readonly string[]): ParsedArgs;
|
|
21
|
+
declare function serializeOpenApi(document: unknown): string;
|
|
22
|
+
declare function validateOpenApiDocument(document: unknown): asserts document is Record<string, unknown>;
|
|
23
|
+
declare function runOpenApiCli(args?: string[], io?: CliIo, overrides?: Partial<OpenApiDeps>): Promise<number>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { parseOpenApiArgs, runOpenApiCli, serializeOpenApi, validateOpenApiDocument };
|
package/dist/openapi.js
CHANGED
|
@@ -15,6 +15,7 @@ Options:
|
|
|
15
15
|
--entry <path> TypeScript API module (default: ./src/api.ts)
|
|
16
16
|
--output <path> YAML artifact (default: ./openapi.yml)
|
|
17
17
|
--check Fail when the artifact is missing or byte-stale; never write
|
|
18
|
+
--json Print machine-readable command results
|
|
18
19
|
--help, -h Show help
|
|
19
20
|
`;
|
|
20
21
|
const defaultDeps = {
|
|
@@ -32,17 +33,19 @@ function parseOpenApiArgs(args) {
|
|
|
32
33
|
entry: "./src/api.ts",
|
|
33
34
|
output: "./openapi.yml",
|
|
34
35
|
check: false,
|
|
35
|
-
help: false
|
|
36
|
+
help: false,
|
|
37
|
+
json: false
|
|
36
38
|
};
|
|
37
39
|
for (let index = 0; index < args.length; index += 1) {
|
|
38
40
|
const value = args[index];
|
|
39
41
|
if (value === "--entry" || value === "--output") {
|
|
40
42
|
const next = args[index + 1];
|
|
41
|
-
if (!next) throw new Error(`${value} requires a path`);
|
|
43
|
+
if (!next || next.startsWith("-")) throw new Error(`${value} requires a path`);
|
|
42
44
|
if (value === "--entry") parsed.entry = next;
|
|
43
45
|
else parsed.output = next;
|
|
44
46
|
index += 1;
|
|
45
47
|
} else if (value === "--check") parsed.check = true;
|
|
48
|
+
else if (value === "--json") parsed.json = true;
|
|
46
49
|
else if (value === "--help" || value === "-h") parsed.help = true;
|
|
47
50
|
else throw new Error(`Unknown option: ${value}`);
|
|
48
51
|
}
|
|
@@ -62,9 +65,18 @@ function serializeOpenApi(document) {
|
|
|
62
65
|
noRefs: true
|
|
63
66
|
}).replace(/\n+$/, "")}\n`;
|
|
64
67
|
}
|
|
65
|
-
|
|
66
|
-
const document = exporterFrom(await deps.importModule(entry)).toOpenApiDocument();
|
|
68
|
+
function validateOpenApiDocument(document) {
|
|
67
69
|
if (!document || typeof document !== "object" || Array.isArray(document)) throw new Error("toOpenApiDocument() must return an OpenAPI document object");
|
|
70
|
+
const candidate = document;
|
|
71
|
+
if (typeof candidate.openapi !== "string" || !/^3\.(?:0|1)\.\d+(?:[-+].+)?$/.test(candidate.openapi)) throw new Error("OpenAPI document must declare a supported openapi 3.0.x or 3.1.x version");
|
|
72
|
+
if (!candidate.info || typeof candidate.info !== "object" || Array.isArray(candidate.info)) throw new Error("OpenAPI document must contain an info object");
|
|
73
|
+
const info = candidate.info;
|
|
74
|
+
if (typeof info.title !== "string" || !info.title.trim() || typeof info.version !== "string" || !info.version.trim()) throw new Error("OpenAPI info must contain non-empty title and version strings");
|
|
75
|
+
if (!candidate.paths || typeof candidate.paths !== "object" || Array.isArray(candidate.paths)) throw new Error("OpenAPI document must contain a paths object");
|
|
76
|
+
}
|
|
77
|
+
async function generate(entry, deps) {
|
|
78
|
+
const document = await exporterFrom(await deps.importModule(entry)).toOpenApiDocument();
|
|
79
|
+
validateOpenApiDocument(document);
|
|
68
80
|
return serializeOpenApi(document);
|
|
69
81
|
}
|
|
70
82
|
async function atomicWrite(output, contents, deps) {
|
|
@@ -83,6 +95,7 @@ async function runOpenApiCli(args = process.argv.slice(2), io = console, overrid
|
|
|
83
95
|
...defaultDeps,
|
|
84
96
|
...overrides
|
|
85
97
|
};
|
|
98
|
+
const wantsJson = args.includes("--json");
|
|
86
99
|
try {
|
|
87
100
|
const parsed = parseOpenApiArgs(args);
|
|
88
101
|
if (parsed.help) {
|
|
@@ -91,6 +104,7 @@ async function runOpenApiCli(args = process.argv.slice(2), io = console, overrid
|
|
|
91
104
|
}
|
|
92
105
|
const entry = path.resolve(deps.cwd(), parsed.entry);
|
|
93
106
|
const output = path.resolve(deps.cwd(), parsed.output);
|
|
107
|
+
if (entry === output) throw new Error("OpenAPI output must not overwrite its source entry");
|
|
94
108
|
const expected = await generate(entry, deps);
|
|
95
109
|
if (parsed.check) {
|
|
96
110
|
let actual;
|
|
@@ -98,23 +112,43 @@ async function runOpenApiCli(args = process.argv.slice(2), io = console, overrid
|
|
|
98
112
|
actual = await deps.readFile(output);
|
|
99
113
|
} catch (error) {
|
|
100
114
|
if (error.code === "ENOENT") {
|
|
101
|
-
io.error(
|
|
115
|
+
io.error(parsed.json ? JSON.stringify({
|
|
116
|
+
status: "error",
|
|
117
|
+
reason: "missing",
|
|
118
|
+
output
|
|
119
|
+
}) : `OpenAPI artifact is missing: ${output}`);
|
|
102
120
|
return 1;
|
|
103
121
|
}
|
|
104
122
|
throw error;
|
|
105
123
|
}
|
|
106
124
|
if (actual !== expected) {
|
|
107
|
-
io.error(
|
|
125
|
+
io.error(parsed.json ? JSON.stringify({
|
|
126
|
+
status: "error",
|
|
127
|
+
reason: "stale",
|
|
128
|
+
output
|
|
129
|
+
}) : `OpenAPI artifact is stale: ${output}`);
|
|
108
130
|
return 1;
|
|
109
131
|
}
|
|
110
|
-
io.log(
|
|
132
|
+
io.log(parsed.json ? JSON.stringify({
|
|
133
|
+
status: "ok",
|
|
134
|
+
action: "checked",
|
|
135
|
+
output
|
|
136
|
+
}) : `OpenAPI artifact is current: ${output}`);
|
|
111
137
|
return 0;
|
|
112
138
|
}
|
|
113
139
|
await atomicWrite(output, expected, deps);
|
|
114
|
-
io.log(
|
|
140
|
+
io.log(parsed.json ? JSON.stringify({
|
|
141
|
+
status: "ok",
|
|
142
|
+
action: "generated",
|
|
143
|
+
output
|
|
144
|
+
}) : `Generated OpenAPI artifact: ${output}`);
|
|
115
145
|
return 0;
|
|
116
146
|
} catch (error) {
|
|
117
|
-
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
io.error(wantsJson ? JSON.stringify({
|
|
149
|
+
status: "error",
|
|
150
|
+
error: message
|
|
151
|
+
}) : message);
|
|
118
152
|
return 1;
|
|
119
153
|
}
|
|
120
154
|
}
|
|
@@ -123,6 +157,4 @@ async function main() {
|
|
|
123
157
|
}
|
|
124
158
|
if (isDirectExecution(import.meta.url)) main();
|
|
125
159
|
//#endregion
|
|
126
|
-
export { parseOpenApiArgs, runOpenApiCli, serializeOpenApi };
|
|
127
|
-
|
|
128
|
-
//# sourceMappingURL=openapi.js.map
|
|
160
|
+
export { parseOpenApiArgs, runOpenApiCli, serializeOpenApi, validateOpenApiDocument };
|
package/dist/planner-VAj7qlxr.js
CHANGED
package/dist/range-YUs9eimn.js
CHANGED
|
@@ -28,7 +28,8 @@ Use this when the app renders outside the browser or produces static output. The
|
|
|
28
28
|
2. Register routes at module load before boot or render.
|
|
29
29
|
3. Keep render paths deterministic and environment-safe.
|
|
30
30
|
4. Use route `entries` or the template's static config for parameterized SSG paths.
|
|
31
|
-
5.
|
|
31
|
+
5. Set the canonical `siteUrl` and explicitly exclude non-indexable routes in `sitemap.routes`.
|
|
32
|
+
6. Build and preview after changes that cross the render boundary.
|
|
32
33
|
|
|
33
34
|
## Copy This Shape
|
|
34
35
|
|
|
@@ -61,6 +62,7 @@ npx @askrjs/cli ssg --config ./ssg.config.ts --output ./dist/static
|
|
|
61
62
|
- SSR or SSG build output is deterministic for the same inputs.
|
|
62
63
|
- Hydration has no structural mismatch warnings.
|
|
63
64
|
- Generated static routes cover expected params.
|
|
65
|
+
- `sitemap.xml` contains every intended canonical route and excludes runtime-only or non-indexable routes.
|
|
64
66
|
- Browser preview still navigates correctly after hydration.
|
|
65
67
|
|
|
66
68
|
## Done When
|