@wssto2/vue-core 0.11.0 → 0.12.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.
- package/CHANGELOG.md +25 -0
- package/README.md +26 -0
- package/bin/architecture/engine.mjs +225 -0
- package/bin/architecture/packs.mjs +107 -0
- package/bin/cli.mjs +57 -0
- package/bin/commands/check-architecture.mjs +38 -0
- package/bin/commands/e2e-timings.mjs +58 -0
- package/bin/commands/feature.mjs +179 -0
- package/bin/commands/lint.mjs +532 -0
- package/bin/config.mjs +72 -0
- package/bin/templates/feature-crud/api.ts.tpl +35 -0
- package/bin/templates/feature-crud/components/__FEATURE_ENTITY__Modal.vue.tpl +36 -0
- package/bin/templates/feature-crud/form.ts.tpl +6 -0
- package/bin/templates/feature-crud/index.ts.tpl +7 -0
- package/bin/templates/feature-crud/routes.ts.tpl +13 -0
- package/bin/templates/feature-crud/tests/scaffold.test.ts.tpl +9 -0
- package/bin/templates/feature-crud/types.ts.tpl +3 -0
- package/bin/templates/feature-crud/views/Index.vue.tpl +36 -0
- package/bin/vue-core.mjs +4 -0
- package/dist/config/index.d.ts +97 -0
- package/dist/config/index.js +6 -0
- package/dist/eslint/index.d.ts +28 -0
- package/dist/eslint/index.js +87 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/modules/identity/entities.d.ts +3 -2
- package/dist/playwright/access.d.ts +61 -0
- package/dist/playwright/envelope.d.ts +37 -0
- package/dist/playwright/index.d.ts +13 -0
- package/dist/playwright/index.js +403 -0
- package/dist/playwright/notifications.d.ts +48 -0
- package/dist/playwright/profile.d.ts +35 -0
- package/dist/playwright/request.d.ts +14 -0
- package/dist/playwright/session.d.ts +48 -0
- package/dist/playwright/users.d.ts +57 -0
- package/package.json +48 -2
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Create__FEATURE_ENTITY__InputSchema, type Create__FEATURE_ENTITY__Input } from "@/generated/routes/__FEATURE_ROUTE_MODULE__/schemas";
|
|
2
|
+
|
|
3
|
+
// The Go input's rules (required, max) come from the generated schema; extend it here for what only the form knows.
|
|
4
|
+
export const __FEATURE_FORM_SCHEMA__ = Create__FEATURE_ENTITY__InputSchema;
|
|
5
|
+
|
|
6
|
+
export type __FEATURE_FORM_TYPE__ = Create__FEATURE_ENTITY__Input;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IconName } from "@wssto2/vue-core/icon";
|
|
2
|
+
|
|
3
|
+
export { __FEATURE_API_CLIENT__, __FEATURE_LIST__ } from "./api";
|
|
4
|
+
export { __FEATURE_FORM_SCHEMA__ } from "./form";
|
|
5
|
+
export type { __FEATURE_FORM_TYPE__ } from "./form";
|
|
6
|
+
export type { __FEATURE_ENTITY__ } from "./types";
|
|
7
|
+
export const MODULE_ICON: IconName = "fileTextLine";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const __FEATURE_API_CLIENT__Routes = [
|
|
2
|
+
{
|
|
3
|
+
name: "__FEATURE_ROUTE_NAME__",
|
|
4
|
+
path: "__FEATURE_ROUTE_PATH__",
|
|
5
|
+
component: () => import("./views/Index.vue"),
|
|
6
|
+
meta: {
|
|
7
|
+
titleKey: "__FEATURE_I18N_MODULE__.title",
|
|
8
|
+
messages: ["__FEATURE_I18N_MODULE__"],
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export default __FEATURE_API_CLIENT__Routes;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { __FEATURE_API_CLIENT__, __FEATURE_LIST__ } from "..";
|
|
3
|
+
|
|
4
|
+
describe("__FEATURE_API_CLIENT__ scaffold", () => {
|
|
5
|
+
it("defines the list over the generated route table", () => {
|
|
6
|
+
const http = {} as Parameters<typeof __FEATURE_API_CLIENT__>[0];
|
|
7
|
+
expect(__FEATURE_LIST__(__FEATURE_API_CLIENT__(http)).id).toBe("__FEATURE_I18N_MODULE__");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { CollectionPage, useCollection, type CollectionColumns } from "@wssto2/vue-core/collection";
|
|
3
|
+
import type { PageAction } from "@wssto2/vue-core/page";
|
|
4
|
+
import { usePlatform } from "@wssto2/vue-core/platform";
|
|
5
|
+
import { computed, ref } from "vue";
|
|
6
|
+
import { useI18n } from "vue-i18n";
|
|
7
|
+
import { MODULE_ICON } from "@/features/__FEATURE_PATH__";
|
|
8
|
+
import { __FEATURE_API_CLIENT__, __FEATURE_LIST__, type __FEATURE_ENTITY__Sort } from "../api";
|
|
9
|
+
import type { __FEATURE_ENTITY__ } from "../types";
|
|
10
|
+
import __FEATURE_ENTITY__Modal from "../components/__FEATURE_ENTITY__Modal.vue";
|
|
11
|
+
|
|
12
|
+
const { t } = useI18n();
|
|
13
|
+
const { access, http } = usePlatform();
|
|
14
|
+
|
|
15
|
+
const featureModal = ref<InstanceType<typeof __FEATURE_ENTITY__Modal> | null>(null);
|
|
16
|
+
|
|
17
|
+
// One columns array drives the table and the phone row: `mobile` says what a column is on a phone.
|
|
18
|
+
const columns = computed(() => [
|
|
19
|
+
{ key: "name", label: t("__FEATURE_I18N_MODULE__.table_columns.name"), kind: "identity", sort: "name", mobile: "primary", subtitle: (item: __FEATURE_ENTITY__) => `ID: ${item.id}` },
|
|
20
|
+
{ key: "created_at", label: t("general.table_columns.created_at"), kind: "timestamp", sort: "created_at", width: 150, align: "center", mobile: "hidden" },
|
|
21
|
+
{ key: "updated_at", label: t("general.table_columns.updated_at"), kind: "timestamp", sort: "updated_at", width: 150, align: "center", mobile: "hidden" },
|
|
22
|
+
] satisfies CollectionColumns<__FEATURE_ENTITY__, __FEATURE_ENTITY__Sort>);
|
|
23
|
+
|
|
24
|
+
const collection = useCollection(__FEATURE_LIST__(__FEATURE_API_CLIENT__(http)), { columns, state: { kind: "memory" } });
|
|
25
|
+
|
|
26
|
+
const actions = computed<PageAction[]>(() => (access.can("__FEATURE_I18N_MODULE__:manage") ? [{
|
|
27
|
+
id: "create", label: t("__FEATURE_I18N_MODULE__.actions.create"), icon: "addLine", placement: "primary", compact: "icon",
|
|
28
|
+
keyboardShortcut: { key: "N", ctrlKey: true }, onClick: () => featureModal.value?.show(),
|
|
29
|
+
}] : []));
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<CollectionPage :collection="collection" :title="t('__FEATURE_I18N_MODULE__.title')" :description="t('__FEATURE_I18N_MODULE__.description')" :icon="MODULE_ICON" :actions="actions">
|
|
34
|
+
<__FEATURE_ENTITY__Modal ref="featureModal" @changed="collection.refresh()" />
|
|
35
|
+
</CollectionPage>
|
|
36
|
+
</template>
|
package/bin/vue-core.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** A debt ESLint cannot count itself (an inline `eslint-disable` is invisible to the rule it silences), ratcheted like a rule. */
|
|
2
|
+
export interface LintPatternCounter {
|
|
3
|
+
/** The key in the baseline file and the line in the report. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** File extensions scanned, with the dot: `[".vue", ".ts"]`. */
|
|
6
|
+
extensions: string[];
|
|
7
|
+
/** Matched line by line; give it the `g` flag. */
|
|
8
|
+
pattern: RegExp;
|
|
9
|
+
/** Printed beside every match in `--report` and in a ratchet failure. */
|
|
10
|
+
hint: string;
|
|
11
|
+
}
|
|
12
|
+
export interface LintConfig {
|
|
13
|
+
/** The baseline file, relative to the config file's folder. Default `scripts/lint-baselines.json`. */
|
|
14
|
+
baselines?: string;
|
|
15
|
+
/** Folders (by name, anywhere in the tree) that are not scanned and whose files are never in the typed scope. Default `["generated"]`. */
|
|
16
|
+
generated?: string[];
|
|
17
|
+
/** Default none. */
|
|
18
|
+
patternCounters?: LintPatternCounter[];
|
|
19
|
+
/** What the messages tell the developer to run, for an application that wraps the command in npm scripts. Each defaults to the `vue-core lint` call. */
|
|
20
|
+
commands?: {
|
|
21
|
+
update?: string;
|
|
22
|
+
typedUpdate?: string;
|
|
23
|
+
typedReport?: string;
|
|
24
|
+
typedReportAll?: string;
|
|
25
|
+
};
|
|
26
|
+
/** Where raising a ceiling is decided, named in the ratchet failure ("… is a CODEOWNERS decision — see <policy>"). */
|
|
27
|
+
policy?: string;
|
|
28
|
+
}
|
|
29
|
+
/** What an application's own architecture rule is given for each source file (.ts / .vue under the config's folder). */
|
|
30
|
+
export interface ArchitectureFile {
|
|
31
|
+
/** path from the config file's folder, with `/` */
|
|
32
|
+
file: string;
|
|
33
|
+
content: string;
|
|
34
|
+
/** the inside of the `<template>` of a .vue file, else "" */
|
|
35
|
+
template: string;
|
|
36
|
+
/** import / re-export specifiers, in the order they appear */
|
|
37
|
+
imports: string[];
|
|
38
|
+
isVue: boolean;
|
|
39
|
+
/** under a tests/ folder or named *.test.* / *.spec.* */
|
|
40
|
+
isTest: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** An application's own rule: the messages for one file (each is prefixed with the file's path), or none. */
|
|
43
|
+
export type ArchitectureRule = (file: ArchitectureFile) => string[] | undefined;
|
|
44
|
+
export interface ArchitectureConfig {
|
|
45
|
+
/** The folder of the features, relative to the config file (default "features"). */
|
|
46
|
+
featuresDir?: string;
|
|
47
|
+
/** The import alias that points at the config file's folder (default `@/`). */
|
|
48
|
+
alias?: string;
|
|
49
|
+
/** Folders that import no feature (default components, composables). */
|
|
50
|
+
sharedLayers?: string[];
|
|
51
|
+
/** Folders (inside the config's folder) the walk skips, by name (default dist, generated, node_modules). */
|
|
52
|
+
skip?: string[];
|
|
53
|
+
/** Folders the "design-values" pack checks (default the features folder and "components"). */
|
|
54
|
+
uiDirs?: string[];
|
|
55
|
+
/**
|
|
56
|
+
* Generated code: its folder under the alias and the subpaths code may import ("entities/*" a folder, "permissions" one module;
|
|
57
|
+
* default entities, schemas, routes). `moved` maps an old import prefix to its new home (old prefix to new home, each with the alias).
|
|
58
|
+
*/
|
|
59
|
+
generated?: {
|
|
60
|
+
dir?: string;
|
|
61
|
+
allowed?: string[];
|
|
62
|
+
moved?: Record<string, string>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Known offenders that predate a rule, by rule: "deep-import" (`<file> -> <specifier>`), "raw-http", "feature-name" and
|
|
66
|
+
* "feature-shape" (paths), and a pack rule's id (files; a stale entry is a violation, so the list only shrinks).
|
|
67
|
+
*/
|
|
68
|
+
allow?: Record<string, string[]>;
|
|
69
|
+
/** Opt-in rule packs: "design-values" (theme tokens) and "presentation" (use the library's components). */
|
|
70
|
+
packs?: ("design-values" | "presentation")[];
|
|
71
|
+
/** The application's own rules, run on every source file. */
|
|
72
|
+
rules?: ArchitectureRule[];
|
|
73
|
+
/** A document to name after a rule's message, by rule: "shared-layer", "api-error" or a pack rule's id. */
|
|
74
|
+
doc?: Record<string, string>;
|
|
75
|
+
}
|
|
76
|
+
export interface GeneratorConfig {
|
|
77
|
+
/** The template folder (`*.tpl` files, `__FEATURE_*__` placeholders), relative to the config's folder. Default: the one shipped with vue-core. */
|
|
78
|
+
template?: string;
|
|
79
|
+
/** Where feature folders are made, relative to the config's folder. Default: `"features"`. */
|
|
80
|
+
out?: string;
|
|
81
|
+
}
|
|
82
|
+
export interface E2eTimingsConfig {
|
|
83
|
+
/** The Playwright JSON report (`reporter: [["json", { outputFile }]]`), relative to the config's folder. Default: `"e2e-timings/results.json"`. */
|
|
84
|
+
report?: string;
|
|
85
|
+
}
|
|
86
|
+
export interface VueCoreConfig {
|
|
87
|
+
/** `vue-core lint` */
|
|
88
|
+
lint?: LintConfig;
|
|
89
|
+
/** `vue-core check-architecture` */
|
|
90
|
+
architecture?: ArchitectureConfig;
|
|
91
|
+
/** `vue-core feature` */
|
|
92
|
+
generator?: GeneratorConfig;
|
|
93
|
+
/** `vue-core e2e-timings` */
|
|
94
|
+
e2eTimings?: E2eTimingsConfig;
|
|
95
|
+
}
|
|
96
|
+
/** Types a `vue-core.config.mjs` (`export default defineConfig({ … })`); returns the config unchanged. */
|
|
97
|
+
export declare function defineConfig(config: VueCoreConfig): VueCoreConfig;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Linter } from "eslint";
|
|
2
|
+
/**
|
|
3
|
+
* The rules that require type information. An explicit list rather than something derived at runtime: the driver once read
|
|
4
|
+
* it back out of the baseline file, which is empty on a first run, so every typed rule was filed under the syntactic section,
|
|
5
|
+
* where the fast path then compared it against a count it never measured.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TYPED_RULES: readonly string[];
|
|
8
|
+
export declare const TYPED_RULE_SET: ReadonlySet<string>;
|
|
9
|
+
export interface VueCoreConfigOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Turn on the type-aware rules (`TYPED_RULES`, at `warn`: the ratchet counts them). Type-aware linting is slow, so it is
|
|
12
|
+
* opt-in. The application decides how: `typed: process.env.LINT_TYPED === "1"`, the variable `vue-core lint` is run with. Default `false`.
|
|
13
|
+
*/
|
|
14
|
+
typed?: boolean;
|
|
15
|
+
/** The folder of the tsconfig the project service reads. Required when `typed`; usually `import.meta.dirname`. */
|
|
16
|
+
tsconfigRootDir?: string;
|
|
17
|
+
/** Ignored in addition to `dist`, `node_modules`, `coverage` and `**\/*.d.ts` (for example the generated code). */
|
|
18
|
+
ignores?: string[];
|
|
19
|
+
/** Your own rule choices; they win over the defaults here, the typed rules included (`{ "…/no-floating-promises": "error" }`). */
|
|
20
|
+
rules?: Linter.RulesRecord;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The flat config: ESLint, typescript-eslint and Vue recommended sets, browser and Node globals, the Vue template parser on
|
|
24
|
+
* TypeScript, the template-formatting rules off (not enforced without a Prettier pass over templates), and the typed block.
|
|
25
|
+
* Spread it and add your own objects after it:
|
|
26
|
+
* `export default [...vueCoreConfig({ typed, tsconfigRootDir: import.meta.dirname, rules: { … } }), { files: […], rules: … }]`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function vueCoreConfig({ typed, tsconfigRootDir, ignores, rules }?: VueCoreConfigOptions): Linter.Config[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import l from "@eslint/js";
|
|
2
|
+
import u from "eslint-plugin-vue";
|
|
3
|
+
import r from "globals";
|
|
4
|
+
import t from "typescript-eslint";
|
|
5
|
+
import a from "vue-eslint-parser";
|
|
6
|
+
const o = [
|
|
7
|
+
"@typescript-eslint/await-thenable",
|
|
8
|
+
"@typescript-eslint/no-base-to-string",
|
|
9
|
+
"@typescript-eslint/no-floating-promises",
|
|
10
|
+
"@typescript-eslint/no-misused-promises",
|
|
11
|
+
"@typescript-eslint/no-redundant-type-constituents",
|
|
12
|
+
"@typescript-eslint/no-unnecessary-type-assertion",
|
|
13
|
+
"@typescript-eslint/no-unsafe-argument",
|
|
14
|
+
"@typescript-eslint/no-unsafe-assignment",
|
|
15
|
+
"@typescript-eslint/no-unsafe-call",
|
|
16
|
+
"@typescript-eslint/no-unsafe-enum-comparison",
|
|
17
|
+
"@typescript-eslint/no-unsafe-member-access",
|
|
18
|
+
"@typescript-eslint/no-unsafe-return",
|
|
19
|
+
"@typescript-eslint/prefer-promise-reject-errors",
|
|
20
|
+
"@typescript-eslint/require-await",
|
|
21
|
+
"@typescript-eslint/restrict-plus-operands",
|
|
22
|
+
"@typescript-eslint/restrict-template-expressions",
|
|
23
|
+
"@typescript-eslint/unbound-method"
|
|
24
|
+
], y = new Set(o);
|
|
25
|
+
function b({ typed: e = !1, tsconfigRootDir: n, ignores: i = [], rules: p = {} } = {}) {
|
|
26
|
+
if (e && !n) throw new Error("vueCoreConfig: typed linting needs tsconfigRootDir (usually import.meta.dirname)");
|
|
27
|
+
return [
|
|
28
|
+
{ ignores: ["dist/**", "node_modules/**", "coverage/**", "**/*.d.ts", ...i] },
|
|
29
|
+
l.configs.recommended,
|
|
30
|
+
...t.configs.recommended,
|
|
31
|
+
...u.configs["flat/recommended"],
|
|
32
|
+
{ files: ["**/*.{js,mjs,ts,vue}"], languageOptions: { globals: { ...r.browser, ...r.node } } },
|
|
33
|
+
// `recommended` alone cannot see types, so it misses no-floating-promises (an unawaited call swallows its rejection),
|
|
34
|
+
// no-misused-promises, await-thenable and require-await.
|
|
35
|
+
...e ? t.configs.recommendedTypeChecked.map((s) => ({ ...s, files: ["**/*.{ts,vue}"] })) : [],
|
|
36
|
+
{
|
|
37
|
+
files: ["**/*.vue"],
|
|
38
|
+
languageOptions: {
|
|
39
|
+
parser: a,
|
|
40
|
+
parserOptions: { parser: t.parser, sourceType: "module", extraFileExtensions: [".vue"] }
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
...e ? [{ files: ["**/*.{ts,vue}"], languageOptions: { parserOptions: { projectService: !0, tsconfigRootDir: n } } }] : [],
|
|
44
|
+
// Config and tooling files are usually outside tsconfig's `include`: no type information, and without this they fail to parse.
|
|
45
|
+
{ files: ["**/*.{js,mjs,cjs}"], ...t.configs.disableTypeChecked },
|
|
46
|
+
{
|
|
47
|
+
files: ["**/*.{ts,vue}"],
|
|
48
|
+
rules: {
|
|
49
|
+
// TypeScript reports undefined references itself, and no-undef cannot see ambient global types.
|
|
50
|
+
"no-undef": "off",
|
|
51
|
+
...e ? Object.fromEntries(o.map((s) => [s, "warn"])) : {},
|
|
52
|
+
// Superseded by @typescript-eslint/no-unused-vars below; the core rule mis-fires on type-only constructs.
|
|
53
|
+
"no-unused-vars": "off",
|
|
54
|
+
"no-useless-assignment": "error",
|
|
55
|
+
"@typescript-eslint/no-empty-object-type": "error",
|
|
56
|
+
"vue/no-ref-as-operand": "error",
|
|
57
|
+
"vue/no-unused-vars": "error",
|
|
58
|
+
"vue/multi-word-component-names": "off",
|
|
59
|
+
// flat/recommended pulls in the template *formatting* rules; the correctness rules of the set stay on.
|
|
60
|
+
"vue/max-attributes-per-line": "off",
|
|
61
|
+
"vue/html-indent": "off",
|
|
62
|
+
"vue/html-closing-bracket-newline": "off",
|
|
63
|
+
"vue/first-attribute-linebreak": "off",
|
|
64
|
+
"vue/singleline-html-element-content-newline": "off",
|
|
65
|
+
"vue/multiline-html-element-content-newline": "off",
|
|
66
|
+
"vue/html-self-closing": "off",
|
|
67
|
+
"vue/attributes-order": "off",
|
|
68
|
+
"vue/no-v-html": "error",
|
|
69
|
+
// `v-model="props.x"` stays an error; shallowOnly lets a controller passed down as a prop expose writable refs.
|
|
70
|
+
"vue/no-mutating-props": ["error", { shallowOnly: !0 }],
|
|
71
|
+
// `_`-prefixed identifiers are a deliberately unused binding.
|
|
72
|
+
"@typescript-eslint/no-unused-vars": [
|
|
73
|
+
"error",
|
|
74
|
+
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_" }
|
|
75
|
+
],
|
|
76
|
+
...p
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
// Test doubles define stub components inline, several per file, with untyped prop lists on purpose.
|
|
80
|
+
{ files: ["**/*.test.ts"], languageOptions: { globals: r.vitest }, rules: { "vue/require-prop-types": "off", "vue/one-component-per-file": "off" } }
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
o as TYPED_RULES,
|
|
85
|
+
y as TYPED_RULE_SET,
|
|
86
|
+
b as vueCoreConfig
|
|
87
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The version of @wssto2/vue-core (kept equal to package.json by index.test.ts). */
|
|
2
|
-
export declare const version = "0.
|
|
2
|
+
export declare const version = "0.12.1";
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ export type ChangeRow = {
|
|
|
22
22
|
export type ChangeView = "all" | "access" | "details";
|
|
23
23
|
export type ClauseInfo = {
|
|
24
24
|
scope: Scope;
|
|
25
|
-
qualifier:
|
|
25
|
+
qualifier: Qualifier;
|
|
26
26
|
attrs?: Record<string, string[]>;
|
|
27
27
|
role?: string;
|
|
28
28
|
binding_id?: number;
|
|
@@ -56,7 +56,7 @@ export type PendingEmail = {
|
|
|
56
56
|
};
|
|
57
57
|
export type PermissionAccess = {
|
|
58
58
|
scope: Scope;
|
|
59
|
-
qualifier:
|
|
59
|
+
qualifier: Qualifier;
|
|
60
60
|
clauses: ClauseInfo[];
|
|
61
61
|
};
|
|
62
62
|
export type PersonRef = {
|
|
@@ -73,6 +73,7 @@ export type ProfileResponse = {
|
|
|
73
73
|
created_at: string;
|
|
74
74
|
pending_email: PendingEmail | null;
|
|
75
75
|
};
|
|
76
|
+
export type Qualifier = "own" | "own_location" | "all";
|
|
76
77
|
export type Scope = {
|
|
77
78
|
level: string;
|
|
78
79
|
id?: number;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Page, Route } from "@playwright/test";
|
|
2
|
+
import type { Binding, Constraint, Grant, PersonRef, Role, RoleHolder, RoleSummary, Scope, ScopeOptions } from "../modules/access/entities";
|
|
3
|
+
import { type Call } from "./request";
|
|
4
|
+
/**
|
|
5
|
+
* go-core's access wire as it serves it under `/api/v1/iam` (the access module): the roles with their holders, compare and replace, the
|
|
6
|
+
* roles bindable at a place, and a person's (or a service account's) access, scopes and bindings. A fake with a little state, so a
|
|
7
|
+
* spec reads what it did through `calls` and the next read shows the change. It knows no role of yours: pass the roles, the places
|
|
8
|
+
* and the bindings your application has.
|
|
9
|
+
*/
|
|
10
|
+
export type { Binding, Constraint, Grant, Role, RoleSummary, Scope };
|
|
11
|
+
export type { Call } from "./request";
|
|
12
|
+
export declare const ORGANIZATION: Scope;
|
|
13
|
+
/** A place below the organization (`scopeOf("dealer", 1, "Auto Zagreb")`). */
|
|
14
|
+
export declare const scopeOf: (level: string, id: number, name: string) => Scope;
|
|
15
|
+
/** A role as go-core sends it: a predefined one by its key, a custom one by its id as the ref. */
|
|
16
|
+
export declare const role: (ref: string, over?: Partial<Role>) => Role;
|
|
17
|
+
export declare const summary: ({ grants: _grants, ...rest }: Role) => RoleSummary;
|
|
18
|
+
export declare const bindingOf: (id: number, of: Role, scope: Scope, createdBy?: PersonRef | null) => Binding;
|
|
19
|
+
export type Hook = (route: Route, url: URL, method: string, body: unknown) => Promise<boolean | void> | boolean | void;
|
|
20
|
+
export type AccessOptions = {
|
|
21
|
+
roles?: Role[];
|
|
22
|
+
holders?: Record<string, RoleHolder[]>;
|
|
23
|
+
/** The roles the actor may give at a place (default: every predefined role). */
|
|
24
|
+
bindable?: Role[];
|
|
25
|
+
/** The bindings of each subject, by `user:3` / `service:21`. */
|
|
26
|
+
bindings?: Record<string, Binding[]>;
|
|
27
|
+
/** The places a role can be given at (default only the organization). */
|
|
28
|
+
places?: ScopeOptions;
|
|
29
|
+
canManage?: boolean;
|
|
30
|
+
/** The id of the next role and binding the page creates (default one past the highest there is). */
|
|
31
|
+
next?: {
|
|
32
|
+
role?: number;
|
|
33
|
+
binding?: number;
|
|
34
|
+
};
|
|
35
|
+
/** Answers first; true means it answered. */
|
|
36
|
+
hook?: Hook;
|
|
37
|
+
};
|
|
38
|
+
export type AccessWire = {
|
|
39
|
+
calls: Call[];
|
|
40
|
+
roles: Role[];
|
|
41
|
+
bindings: Record<string, Binding[]>;
|
|
42
|
+
reads: string[];
|
|
43
|
+
};
|
|
44
|
+
/** A role the server refuses as invalid: `authz.invalid` with its problems as objects. */
|
|
45
|
+
export declare const invalid: (problems: {
|
|
46
|
+
code: string;
|
|
47
|
+
permission?: string;
|
|
48
|
+
detail?: string;
|
|
49
|
+
}[]) => {
|
|
50
|
+
body?: string | Buffer;
|
|
51
|
+
contentType?: string;
|
|
52
|
+
headers?: {
|
|
53
|
+
[key: string]: string;
|
|
54
|
+
};
|
|
55
|
+
json?: any;
|
|
56
|
+
path?: string;
|
|
57
|
+
response?: import("playwright-core").APIResponse;
|
|
58
|
+
status?: number;
|
|
59
|
+
};
|
|
60
|
+
/** Answers go-core's access routes under `/api/v1/iam`. The page needs `mockAuth` too; register this after it. */
|
|
61
|
+
export declare function serveAccess(page: Page, options?: AccessOptions): Promise<AccessWire>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Route } from "@playwright/test";
|
|
2
|
+
import type { ListResult } from "../client";
|
|
3
|
+
import type { ViewCount } from "../modules/identity/entities";
|
|
4
|
+
/** What `route.fulfill` takes: the status, content type and body of one answer. */
|
|
5
|
+
export type Answer = NonNullable<Parameters<Route["fulfill"]>[0]>;
|
|
6
|
+
/**
|
|
7
|
+
* go-core's success envelope. The library's HTTP client unwraps `data` only from a body that has `success` (the server always
|
|
8
|
+
* sends it), so a fixture for a record the library reads says it too.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ok: <T>(data: T, meta?: unknown) => {
|
|
11
|
+
meta?: {} | null | undefined;
|
|
12
|
+
success: true;
|
|
13
|
+
data: T;
|
|
14
|
+
};
|
|
15
|
+
/** go-core's paged list: the rows as `data`, the numbers (and any views) in `meta`, as `web.Paginated` writes it. */
|
|
16
|
+
export declare const pageOk: (rows: unknown[], meta?: Record<string, unknown>) => {
|
|
17
|
+
meta?: {} | null | undefined;
|
|
18
|
+
success: true;
|
|
19
|
+
data: unknown[];
|
|
20
|
+
};
|
|
21
|
+
/** go-core's datatable answer (a `ListResult`): the rows, the paging and `meta.views`, inside the envelope's `data`. */
|
|
22
|
+
export declare const datatable: <Row>(rows: Row[], views?: ViewCount[], paging?: Partial<Omit<ListResult<Row>, "data" | "meta">>) => {
|
|
23
|
+
meta?: {} | null | undefined;
|
|
24
|
+
success: true;
|
|
25
|
+
data: ListResult<Row>;
|
|
26
|
+
};
|
|
27
|
+
/** A JSON answer for `route.fulfill`. */
|
|
28
|
+
export declare const json: (body: unknown, status?: number) => Answer;
|
|
29
|
+
/** An empty answer (`204`, a delete or a password change). */
|
|
30
|
+
export declare const noContent: () => Answer;
|
|
31
|
+
/** A server-sent-events answer: each event as one `data:` frame. */
|
|
32
|
+
export declare const sse: (events: unknown[]) => Answer;
|
|
33
|
+
/** A refusal as go-core sends it: the reason as `code`, its params, and the field it belongs to. */
|
|
34
|
+
export declare const refusal: (status: number, code: string, extra?: {
|
|
35
|
+
params?: unknown;
|
|
36
|
+
field?: string;
|
|
37
|
+
}) => Answer;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { datatable, json, noContent, ok, pageOk, refusal, sse } from "./envelope";
|
|
2
|
+
export type { Answer } from "./envelope";
|
|
3
|
+
export { accessOf, authBody, authResponse, defaultUser, mockAuth } from "./session";
|
|
4
|
+
export type { HeldAt, MockAuthOptions, SessionExtra } from "./session";
|
|
5
|
+
export { ORGANIZATION, bindingOf, invalid, role, scopeOf, serveAccess, summary } from "./access";
|
|
6
|
+
export type { AccessOptions, AccessWire, Binding, Constraint, Grant, Hook, Role, RoleSummary, Scope } from "./access";
|
|
7
|
+
export { device, notification, serveNotifications, setting } from "./notifications";
|
|
8
|
+
export type { CategorySettings, Device, Item, QuietHours, Setting, Wire, WireOptions } from "./notifications";
|
|
9
|
+
export { NEW_EMAIL, ownSession, ownSignin, pendingChange, profile, serveProfile } from "./profile";
|
|
10
|
+
export type { ProfileAnswers, ProfileCall, ProfileOptions } from "./profile";
|
|
11
|
+
export { activity, ago, change, freshState, person, serveUsers, serviceAccount, session, signin } from "./users";
|
|
12
|
+
export type { UsersOptions, UsersRequest, UsersState, Write } from "./users";
|
|
13
|
+
export type { Call } from "./request";
|