@reqquest/ui 1.16.0 → 1.18.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/app/env.template +5 -0
- package/app/runtime/apply-env.sh +10 -0
- package/app/runtime/nginx.conf +37 -0
- package/app/src/app.css +21 -0
- package/app/src/app.d.ts +13 -0
- package/app/src/app.html +12 -0
- package/app/src/internal/api.ts +1707 -0
- package/app/src/internal/appreq-utils.ts +33 -0
- package/app/src/internal/components/AnnouncementBanner.svelte +47 -0
- package/app/src/internal/components/AppRequestCard.svelte +173 -0
- package/app/src/internal/components/ApplicantOptOutModal.svelte +78 -0
- package/app/src/internal/components/ApplicantProgramList.svelte +276 -0
- package/app/src/internal/components/ApplicantProgramListTooltip.svelte +44 -0
- package/app/src/internal/components/ApplicantPromptPage.svelte +136 -0
- package/app/src/internal/components/ApplicantPromptSkeleton.svelte +12 -0
- package/app/src/internal/components/ApplicationDetailsView.svelte +369 -0
- package/app/src/internal/components/ButtonLoadingIcon.svelte +28 -0
- package/{dist → app/src/internal}/components/DelayedSkeleton.svelte +2 -2
- package/app/src/internal/components/FieldNestedMultiselect.svelte +270 -0
- package/app/src/internal/components/IntroPanel.svelte +41 -0
- package/app/src/internal/components/MissingDefinitionNotification.svelte +20 -0
- package/app/src/internal/components/PeriodPanel.svelte +125 -0
- package/app/src/internal/components/ProgramStatusCell.svelte +134 -0
- package/app/src/internal/components/RenderDisplayComponent.svelte +42 -0
- package/app/src/internal/components/ReviewerActions.svelte +171 -0
- package/app/src/internal/components/ReviewerQuestions.svelte +444 -0
- package/app/src/internal/components/StatusMessageList.svelte +54 -0
- package/app/src/internal/components/SystemDeterminationNotice.svelte +37 -0
- package/app/src/internal/components/TabLinks.svelte +265 -0
- package/app/src/internal/components/WarningIconYellow.svelte +22 -0
- package/app/src/internal/components/index.ts +19 -0
- package/app/src/internal/csv.ts +30 -0
- package/app/src/internal/index.ts +8 -0
- package/app/src/internal/prompt-save-queue.ts +91 -0
- package/app/src/internal/prompt-utils.ts +40 -0
- package/app/src/internal/status-utils.ts +503 -0
- package/app/src/internal/stores/IStateStore.ts +5 -0
- package/app/src/internal/util.ts +27 -0
- package/app/src/routes/+error.svelte +4 -0
- package/app/src/routes/+layout.svelte +134 -0
- package/app/src/routes/+layout.ts +18 -0
- package/app/src/routes/+page.ts +14 -0
- package/app/src/routes/announcement/+page.svelte +164 -0
- package/app/src/routes/announcement/+page.ts +16 -0
- package/app/src/routes/dashboards/applicant/+page.svelte +494 -0
- package/app/src/routes/dashboards/applicant/+page.ts +95 -0
- package/app/src/routes/dashboards/reviewer/+page.svelte +143 -0
- package/app/src/routes/dashboards/reviewer/+page.ts +43 -0
- package/app/src/routes/periods/+page.svelte +158 -0
- package/app/src/routes/periods/+page.ts +13 -0
- package/app/src/routes/periods/[id]/configure/+page.svelte +150 -0
- package/app/src/routes/periods/[id]/configure/+page.ts +10 -0
- package/app/src/routes/requests/+page.svelte +246 -0
- package/app/src/routes/requests/+page.ts +24 -0
- package/app/src/routes/requests/[id]/+layout.ts +11 -0
- package/app/src/routes/requests/[id]/accept/+layout.svelte +80 -0
- package/app/src/routes/requests/[id]/accept/+layout.ts +12 -0
- package/app/src/routes/requests/[id]/accept/+page.ts +13 -0
- package/app/src/routes/requests/[id]/accept/[promptId]/+page.svelte +7 -0
- package/app/src/routes/requests/[id]/accept/[promptId]/+page.ts +10 -0
- package/app/src/routes/requests/[id]/accept/review/+page.svelte +87 -0
- package/app/src/routes/requests/[id]/apply/+layout.svelte +181 -0
- package/app/src/routes/requests/[id]/apply/+layout.ts +12 -0
- package/app/src/routes/requests/[id]/apply/+page.ts +14 -0
- package/app/src/routes/requests/[id]/apply/ProgressNavContainer.svelte +42 -0
- package/app/src/routes/requests/[id]/apply/[promptId]/+page.svelte +23 -0
- package/app/src/routes/requests/[id]/apply/[promptId]/+page.ts +11 -0
- package/app/src/routes/requests/[id]/apply/programs/+page.svelte +71 -0
- package/app/src/routes/requests/[id]/apply/review/+page.svelte +102 -0
- package/app/src/routes/requests/[id]/approve/+layout.svelte +114 -0
- package/app/src/routes/requests/[id]/approve/+page.ts +14 -0
- package/app/src/routes/requests/[id]/approve/ApproveLayout.svelte +164 -0
- package/app/src/routes/requests/[id]/approve/[programKey]/+page.svelte +396 -0
- package/app/src/routes/requests/[id]/approve/[programKey]/+page.ts +15 -0
- package/app/src/routes/requests/[id]/approve/activity/+page.svelte +66 -0
- package/app/src/routes/requests/[id]/approve/activity/+page.ts +12 -0
- package/app/src/routes/requests/[id]/approve/export/+page.ts +12 -0
- package/app/src/routes/requests/[id]/approve/export/+page@[id].svelte +41 -0
- package/app/src/routes/requests/[id]/export/+page.svelte +32 -0
- package/app/src/routes/requests/[id]/export/+page.ts +12 -0
- package/app/src/routes/roles/+layout.svelte +16 -0
- package/app/src/routes/roles/+layout.ts +14 -0
- package/app/src/routes/roles/+page.svelte +225 -0
- package/app/src/routes/roles/+page.ts +13 -0
- package/app/src/routes/roles/[id]/+layout.ts +10 -0
- package/app/src/routes/roles/[id]/+page.svelte +335 -0
- package/app/src/routes/roles/[id]/+page.ts +10 -0
- package/app/src/routes/roles/[id]/ControlWithTooltip.svelte +15 -0
- package/app/src/routes/roles/[id]/TagsWithTooltip.svelte +42 -0
- package/app/src/routes/roles/users/+layout.ts +13 -0
- package/app/src/routes/roles/users/+page.svelte +46 -0
- package/app/src/routes/roles/users/+page.ts +90 -0
- package/app/static/favicon.png +0 -0
- package/bin/reqquest-ui.js +117 -0
- package/cli/config.js +105 -0
- package/cli/sync.js +296 -0
- package/cli/util.js +29 -0
- package/dist/components.md +0 -4
- package/dist/registry.js +1 -1
- package/dist/typed-client/schema.d.ts +25 -3
- package/dist/typed-client/schema.graphql +18 -2
- package/dist/typed-client/types.d.ts +9 -1
- package/dist/typed-client/types.js +29 -5
- package/package.json +29 -15
- package/dist/components/DelayedSkeleton.svelte.d.ts +0 -21
package/cli/config.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SvelteKit and Vite configuration for a downstream ReqQuest UI.
|
|
3
|
+
*
|
|
4
|
+
* `reqquest-ui sync` writes .reqquest/svelte.config.js and .reqquest/vite.config.js as two-line stubs
|
|
5
|
+
* that import these functions by absolute path. Keeping the real configuration here, inside the
|
|
6
|
+
* package, means every plugin below resolves from @reqquest/ui's own dependencies - the downstream
|
|
7
|
+
* project never declares vite, the adapter, tailwind or the preprocessors, so their versions are
|
|
8
|
+
* whatever this release of @reqquest/ui was built and tested with.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors ui/svelte.config.js and ui/vite.config.ts; change them together.
|
|
11
|
+
*/
|
|
12
|
+
import { join } from 'node:path'
|
|
13
|
+
import adapter from '@sveltejs/adapter-static'
|
|
14
|
+
import { sveltekit } from '@sveltejs/kit/vite'
|
|
15
|
+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
16
|
+
import carbonSvelteTailwind from '@txstate-mws/carbon-svelte/tailwind'
|
|
17
|
+
import autoprefixer from 'autoprefixer'
|
|
18
|
+
import { optimizeImports } from 'carbon-preprocess-svelte'
|
|
19
|
+
import tailwindcss from 'tailwindcss'
|
|
20
|
+
import { packageDir } from './util.js'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {{ projectRoot: string, appRoot: string, basePath?: string }} opts
|
|
24
|
+
* projectRoot - the downstream project (holds src/local, src/routes, package.json)
|
|
25
|
+
* appRoot - the generated .reqquest/ directory SvelteKit runs in
|
|
26
|
+
* @returns {import('@sveltejs/kit').Config}
|
|
27
|
+
*/
|
|
28
|
+
export function svelteConfig ({ projectRoot, appRoot, basePath = '' }) {
|
|
29
|
+
return {
|
|
30
|
+
preprocess: [vitePreprocess(), optimizeImports()],
|
|
31
|
+
kit: {
|
|
32
|
+
adapter: adapter({
|
|
33
|
+
pages: join(projectRoot, 'build'),
|
|
34
|
+
assets: join(projectRoot, 'build'),
|
|
35
|
+
fallback: 'index.html',
|
|
36
|
+
precompress: true
|
|
37
|
+
}),
|
|
38
|
+
output: {
|
|
39
|
+
preloadStrategy: 'preload-mjs'
|
|
40
|
+
},
|
|
41
|
+
paths: {
|
|
42
|
+
base: basePath
|
|
43
|
+
},
|
|
44
|
+
files: {
|
|
45
|
+
// $lib keeps meaning the project's own src/lib, as it would in any SvelteKit app
|
|
46
|
+
lib: join(projectRoot, 'src/lib')
|
|
47
|
+
},
|
|
48
|
+
alias: {
|
|
49
|
+
$internal: 'src/internal',
|
|
50
|
+
// the downstream registry is not copied: it is aliased in place so the editor, HMR and
|
|
51
|
+
// svelte-check all work on the files the developer actually edits
|
|
52
|
+
$local: join(projectRoot, 'src/local')
|
|
53
|
+
},
|
|
54
|
+
typescript: {
|
|
55
|
+
// SvelteKit's generated tsconfig only includes the app root, which src/local and src/lib sit outside of
|
|
56
|
+
config (tsconfig) {
|
|
57
|
+
for (const dir of ['src/local', 'src/lib']) {
|
|
58
|
+
const abs = join(projectRoot, dir)
|
|
59
|
+
tsconfig.include.push(`${abs}/**/*.ts`, `${abs}/**/*.js`, `${abs}/**/*.svelte`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {{ projectRoot: string, appRoot: string }} opts
|
|
69
|
+
* @returns {import('vite').UserConfig}
|
|
70
|
+
*/
|
|
71
|
+
export function viteConfig ({ projectRoot, appRoot }) {
|
|
72
|
+
const globs = '**/*.{html,js,svelte,ts}'
|
|
73
|
+
return {
|
|
74
|
+
plugins: [sveltekit()],
|
|
75
|
+
build: {
|
|
76
|
+
sourcemap: true
|
|
77
|
+
},
|
|
78
|
+
optimizeDeps: {
|
|
79
|
+
exclude: ['carbon-icons-svelte']
|
|
80
|
+
},
|
|
81
|
+
server: {
|
|
82
|
+
fs: {
|
|
83
|
+
allow: [projectRoot, packageDir('@txstate-mws/carbon-svelte', appRoot)]
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
// inline so that no postcss.config.js / tailwind.config.js is needed in either the project or
|
|
87
|
+
// .reqquest/, and so the plugins resolve from this package rather than the project
|
|
88
|
+
css: {
|
|
89
|
+
postcss: {
|
|
90
|
+
plugins: [
|
|
91
|
+
tailwindcss({
|
|
92
|
+
content: [
|
|
93
|
+
join(appRoot, 'src', globs),
|
|
94
|
+
join(projectRoot, 'src/local', globs),
|
|
95
|
+
join(projectRoot, 'src/lib', globs),
|
|
96
|
+
join(packageDir('@txstate-mws/carbon-svelte', appRoot), globs)
|
|
97
|
+
],
|
|
98
|
+
plugins: [carbonSvelteTailwind]
|
|
99
|
+
}),
|
|
100
|
+
autoprefixer()
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
package/cli/sync.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assembles .reqquest/ - a complete SvelteKit project - from the app shell shipped in this package
|
|
3
|
+
* plus the downstream project's own files.
|
|
4
|
+
*
|
|
5
|
+
* node_modules/@reqquest/ui/app/src/routes base routes (read-only)
|
|
6
|
+
* + <project>/src/routes developer overrides and additions
|
|
7
|
+
* = .reqquest/src/routes what SvelteKit builds
|
|
8
|
+
*
|
|
9
|
+
* Overlay rules, applied in this order:
|
|
10
|
+
* 1. package.json "reqquest": { "disableRoutes": ["roles/users"] } removes those base route folders
|
|
11
|
+
* 2. a `.reqquest-replace` file in a developer folder removes the base folder of the same path, so
|
|
12
|
+
* the developer's version stands alone (use when an override page needs different load data)
|
|
13
|
+
* 3. every developer file is copied over the base; same path = override, new path = addition
|
|
14
|
+
*
|
|
15
|
+
* src/local is never copied - svelteConfig() aliases $local to it in place. src/internal is base-only:
|
|
16
|
+
* every route depends on it, so letting it be overridden would freeze it as public API.
|
|
17
|
+
*
|
|
18
|
+
* Overriding a base file forks it. reqquest-overrides.lock.json (committed, in the project root)
|
|
19
|
+
* records the hash of each base file at the time it was overridden, so an upgrade that changes the
|
|
20
|
+
* file underneath an override is reported instead of silently diverging.
|
|
21
|
+
*
|
|
22
|
+
* The generated tree is reconciled rather than wiped: unchanged files are not rewritten, so a
|
|
23
|
+
* running dev server only sees what actually changed, and .reqquest/.svelte-kit and the Vite cache
|
|
24
|
+
* survive.
|
|
25
|
+
*/
|
|
26
|
+
import { createHash } from 'node:crypto'
|
|
27
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
28
|
+
import { dirname, join, relative, resolve, sep } from 'node:path'
|
|
29
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
30
|
+
import { packageDir, readJson } from './util.js'
|
|
31
|
+
|
|
32
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
33
|
+
const APP = join(PACKAGE_ROOT, 'app')
|
|
34
|
+
const MARKER = '.reqquest-replace'
|
|
35
|
+
const LOCKFILE = 'reqquest-overrides.lock.json'
|
|
36
|
+
const GENERATED_NOTE = 'generated by `reqquest-ui sync` - do not edit; it is rewritten on every sync'
|
|
37
|
+
|
|
38
|
+
export class SyncError extends Error {}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} projectRoot
|
|
42
|
+
* @param {{ strict?: boolean, accept?: boolean, quiet?: boolean }} [opts]
|
|
43
|
+
* strict - fail instead of warn on drift or an out-of-date lockfile (CI / docker builds)
|
|
44
|
+
* accept - re-record the current base hashes in the lockfile, acknowledging any drift
|
|
45
|
+
* @returns {{ appRoot: string, warnings: string[] }}
|
|
46
|
+
*/
|
|
47
|
+
export function sync (projectRoot, { strict = false, accept = false, quiet = false } = {}) {
|
|
48
|
+
if (!existsSync(APP)) throw new SyncError(`${APP} is missing - this @reqquest/ui was not packed with its app shell.`)
|
|
49
|
+
const appRoot = join(projectRoot, '.reqquest')
|
|
50
|
+
const pkg = readJson(join(projectRoot, 'package.json'))
|
|
51
|
+
const options = pkg.reqquest ?? {}
|
|
52
|
+
const warnings = []
|
|
53
|
+
|
|
54
|
+
checkToolchain(projectRoot)
|
|
55
|
+
if (!existsSync(join(projectRoot, 'src/local/index.ts')) && !existsSync(join(projectRoot, 'src/local/index.js'))) {
|
|
56
|
+
throw new SyncError('src/local/index.ts not found. It must export `uiRegistry` - see docs/downstream-ui.md in the reqquest repo.')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @type {Map<string, Buffer>} path relative to appRoot -> contents */
|
|
60
|
+
const files = new Map()
|
|
61
|
+
|
|
62
|
+
// -- src: base shell, then the route overlay
|
|
63
|
+
for (const rel of walk(join(APP, 'src'))) files.set(join('src', rel), readFileSync(join(APP, 'src', rel)))
|
|
64
|
+
const overrides = overlayRoutes(projectRoot, files, options, warnings)
|
|
65
|
+
|
|
66
|
+
// -- static: base, then the project's own
|
|
67
|
+
for (const rel of walk(join(APP, 'static'))) files.set(join('static', rel), readFileSync(join(APP, 'static', rel)))
|
|
68
|
+
for (const rel of walk(join(projectRoot, 'static'))) files.set(join('static', rel), readFileSync(join(projectRoot, 'static', rel)))
|
|
69
|
+
|
|
70
|
+
// -- config stubs and env
|
|
71
|
+
const configModule = pathToFileURL(join(PACKAGE_ROOT, 'cli/config.js')).href
|
|
72
|
+
const configArgs = JSON.stringify({ projectRoot, appRoot, basePath: options.basePath ?? '' })
|
|
73
|
+
files.set('svelte.config.js', Buffer.from(`// ${GENERATED_NOTE}\nimport { svelteConfig } from '${configModule}'\nexport default svelteConfig(${configArgs})\n`))
|
|
74
|
+
files.set('vite.config.js', Buffer.from(`// ${GENERATED_NOTE}\nimport { viteConfig } from '${configModule}'\nexport default viteConfig(${configArgs})\n`))
|
|
75
|
+
files.set('tsconfig.json', Buffer.from(JSON.stringify({
|
|
76
|
+
'//': GENERATED_NOTE,
|
|
77
|
+
extends: './.svelte-kit/tsconfig.json',
|
|
78
|
+
compilerOptions: { sourceMap: true, strictNullChecks: true, skipLibCheck: true }
|
|
79
|
+
}, null, 2) + '\n'))
|
|
80
|
+
// vite-plugin-svelte crawls the root package.json's dependencies to find svelte libraries to
|
|
81
|
+
// prebundle in dev, so list what @reqquest/ui's own package.json would - they resolve upward from here
|
|
82
|
+
const ui = readJson(join(PACKAGE_ROOT, 'package.json'))
|
|
83
|
+
files.set('package.json', Buffer.from(JSON.stringify({
|
|
84
|
+
'//': GENERATED_NOTE,
|
|
85
|
+
name: `${pkg.name ?? 'app'}-reqquest`,
|
|
86
|
+
private: true,
|
|
87
|
+
type: 'module',
|
|
88
|
+
dependencies: { '@reqquest/ui': ui.version, ...ui.dependencies, ...pkg.dependencies },
|
|
89
|
+
devDependencies: ui.peerDependencies
|
|
90
|
+
}, null, 2) + '\n'))
|
|
91
|
+
files.set('.env', mergeEnv(readFileSync(join(APP, 'env.template'), 'utf8'), join(projectRoot, '.env')))
|
|
92
|
+
|
|
93
|
+
reconcile(appRoot, files, ['src', 'static'])
|
|
94
|
+
updateLock(projectRoot, overrides, { strict, accept, warnings })
|
|
95
|
+
ensureRootTsconfig(projectRoot, warnings)
|
|
96
|
+
|
|
97
|
+
if (!quiet) for (const w of warnings) console.warn(`reqquest-ui: ${w}`)
|
|
98
|
+
return { appRoot, warnings }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The toolchain packages that must be single, top-level copies (svelte, kit, vite, the svelte vite
|
|
103
|
+
* plugin) are exact-pinned peers of @reqquest/ui. npm installs them for the project, but if the
|
|
104
|
+
* project's own package.json declares a different version npm only warns ("ERESOLVE overriding peer
|
|
105
|
+
* dependency") and installs that one. This is the check that actually stops the drift, and every
|
|
106
|
+
* reqquest-ui command runs it.
|
|
107
|
+
*/
|
|
108
|
+
function checkToolchain (projectRoot) {
|
|
109
|
+
const { peerDependencies = {}, version } = readJson(join(PACKAGE_ROOT, 'package.json'))
|
|
110
|
+
for (const [name, wanted] of Object.entries(peerDependencies)) {
|
|
111
|
+
let installed
|
|
112
|
+
try {
|
|
113
|
+
installed = readJson(join(packageDir(name, join(projectRoot, '.reqquest')), 'package.json')).version
|
|
114
|
+
} catch {
|
|
115
|
+
throw new SyncError(`${name} is not installed. It is a peer dependency of @reqquest/ui - run npm install.`)
|
|
116
|
+
}
|
|
117
|
+
if (installed !== wanted) {
|
|
118
|
+
throw new SyncError(`@reqquest/ui ${version} requires ${name} ${wanted} but ${installed} is installed. ` +
|
|
119
|
+
`Remove ${name} from your package.json - @reqquest/ui provides it - and reinstall.`)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Applies the developer's src/routes onto the base routes already in `files`.
|
|
126
|
+
* @returns {Map<string, string>} base route path (relative to routes/) -> hash of the base content
|
|
127
|
+
* the developer has forked, either by overriding the file or by replacing its folder
|
|
128
|
+
*/
|
|
129
|
+
function overlayRoutes (projectRoot, files, options, warnings) {
|
|
130
|
+
const routesKey = join('src', 'routes')
|
|
131
|
+
const baseRoutes = new Map()
|
|
132
|
+
for (const [path, content] of files) {
|
|
133
|
+
if (path.startsWith(routesKey + sep)) baseRoutes.set(relative(routesKey, path), content)
|
|
134
|
+
}
|
|
135
|
+
const deleteBase = (prefix) => {
|
|
136
|
+
let found = false
|
|
137
|
+
for (const rel of baseRoutes.keys()) {
|
|
138
|
+
if (rel === prefix || rel.startsWith(prefix + sep)) {
|
|
139
|
+
files.delete(join(routesKey, rel))
|
|
140
|
+
found = true
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return found
|
|
144
|
+
}
|
|
145
|
+
const forked = new Map()
|
|
146
|
+
|
|
147
|
+
for (const route of options.disableRoutes ?? []) {
|
|
148
|
+
const prefix = normalizeRoute(route)
|
|
149
|
+
if (!deleteBase(prefix)) warnings.push(`disableRoutes: "${route}" matches no ReqQuest route - check the spelling.`)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const devRoot = join(projectRoot, 'src', 'routes')
|
|
153
|
+
const devFiles = walk(devRoot)
|
|
154
|
+
|
|
155
|
+
for (const rel of devFiles) {
|
|
156
|
+
if (rel.split(sep).pop() !== MARKER) continue
|
|
157
|
+
const prefix = dirname(rel) === '.' ? '' : dirname(rel)
|
|
158
|
+
for (const [baseRel, content] of baseRoutes) {
|
|
159
|
+
if (prefix === '' || baseRel.startsWith(prefix + sep)) forked.set(baseRel, hash(content))
|
|
160
|
+
}
|
|
161
|
+
if (prefix === '') {
|
|
162
|
+
for (const baseRel of baseRoutes.keys()) files.delete(join(routesKey, baseRel))
|
|
163
|
+
} else if (!deleteBase(prefix)) {
|
|
164
|
+
warnings.push(`${join('src/routes', rel)} replaces "${prefix}", which is not a ReqQuest route - the marker does nothing.`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const escapes = []
|
|
169
|
+
for (const rel of devFiles) {
|
|
170
|
+
if (rel.split(sep).pop() === MARKER) continue
|
|
171
|
+
const content = readFileSync(join(devRoot, rel))
|
|
172
|
+
escapes.push(...escapingImports(devRoot, rel, content))
|
|
173
|
+
if (baseRoutes.has(rel)) forked.set(rel, hash(baseRoutes.get(rel)))
|
|
174
|
+
files.set(join(routesKey, rel), content)
|
|
175
|
+
}
|
|
176
|
+
if (escapes.length) {
|
|
177
|
+
throw new SyncError('Route files are copied into .reqquest/ before building, so a relative import that leaves src/routes would break:\n' +
|
|
178
|
+
escapes.map(e => ` ${e}`).join('\n') +
|
|
179
|
+
'\nImport through an alias instead: $lib for src/lib, $local for src/local.')
|
|
180
|
+
}
|
|
181
|
+
return forked
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const IMPORT_RE = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s+)(['"])(\.{1,2}\/[^'"]*)\1/g
|
|
185
|
+
|
|
186
|
+
function escapingImports (devRoot, rel, content) {
|
|
187
|
+
if (!/\.(svelte|[cm]?[jt]s)$/.test(rel)) return []
|
|
188
|
+
const found = []
|
|
189
|
+
for (const [, , spec] of content.toString('utf8').matchAll(IMPORT_RE)) {
|
|
190
|
+
const target = resolve(devRoot, dirname(rel), spec)
|
|
191
|
+
if (relative(devRoot, target).startsWith('..')) found.push(`${join('src/routes', rel)}: '${spec}'`)
|
|
192
|
+
}
|
|
193
|
+
return found
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function updateLock (projectRoot, forked, { strict, accept, warnings }) {
|
|
197
|
+
const lockPath = join(projectRoot, LOCKFILE)
|
|
198
|
+
const previous = existsSync(lockPath) ? readJson(lockPath).files ?? {} : {}
|
|
199
|
+
const problems = []
|
|
200
|
+
const next = {}
|
|
201
|
+
|
|
202
|
+
for (const [path, baseHash] of [...forked].sort(([a], [b]) => a.localeCompare(b))) {
|
|
203
|
+
if (!(path in previous)) {
|
|
204
|
+
problems.push(`new override of src/routes/${path} is not recorded`)
|
|
205
|
+
next[path] = baseHash
|
|
206
|
+
} else if (previous[path] !== baseHash && !accept) {
|
|
207
|
+
warnings.push(`ReqQuest changed src/routes/${path} since you overrode it. Compare your copy with ` +
|
|
208
|
+
`node_modules/@reqquest/ui/app/src/routes/${path}, then run \`reqquest-ui sync --accept\`.`)
|
|
209
|
+
problems.push(`src/routes/${path} drifted from its base`)
|
|
210
|
+
next[path] = previous[path] // keep the old hash so the warning repeats until reviewed
|
|
211
|
+
} else {
|
|
212
|
+
next[path] = baseHash
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const path of Object.keys(previous)) {
|
|
216
|
+
if (!forked.has(path)) problems.push(`src/routes/${path} is listed but no longer an override`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (strict && problems.length) {
|
|
220
|
+
throw new SyncError(`${LOCKFILE} is out of date:\n${problems.map(p => ` ${p}`).join('\n')}\n` +
|
|
221
|
+
'Run `npx reqquest-ui sync` locally (add --accept once you have reviewed any drift) and commit the lockfile.')
|
|
222
|
+
}
|
|
223
|
+
if (JSON.stringify(next) === JSON.stringify(previous)) return
|
|
224
|
+
if (Object.keys(next).length) writeFileSync(lockPath, JSON.stringify({ '//': 'written by `reqquest-ui sync` - commit it', files: next }, null, 2) + '\n')
|
|
225
|
+
else rmSync(lockPath, { force: true })
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function ensureRootTsconfig (projectRoot, warnings) {
|
|
229
|
+
const path = join(projectRoot, 'tsconfig.json')
|
|
230
|
+
const wanted = './.reqquest/tsconfig.json'
|
|
231
|
+
if (!existsSync(path)) {
|
|
232
|
+
writeFileSync(path, JSON.stringify({ extends: wanted }, null, 2) + '\n')
|
|
233
|
+
} else if (!readFileSync(path, 'utf8').includes(wanted)) {
|
|
234
|
+
warnings.push(`tsconfig.json does not extend "${wanted}", so your editor will not resolve $local, $internal or the route types. Replace it with { "extends": "${wanted}" }.`)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Template keys first, project .env overriding by key. */
|
|
239
|
+
function mergeEnv (template, projectEnv) {
|
|
240
|
+
const lines = new Map()
|
|
241
|
+
const add = text => {
|
|
242
|
+
for (const line of text.split('\n')) {
|
|
243
|
+
const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line)
|
|
244
|
+
if (m) lines.set(m[1], line)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
add(template)
|
|
248
|
+
if (existsSync(projectEnv)) add(readFileSync(projectEnv, 'utf8'))
|
|
249
|
+
return Buffer.from([...lines.values()].join('\n') + '\n')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Makes appRoot match `files`. Within `managedDirs`, anything not in `files` is deleted; elsewhere
|
|
254
|
+
* (.svelte-kit, node_modules/.vite) is left alone. Generated files are made read-only so an
|
|
255
|
+
* accidental edit fails instead of being silently overwritten by the next sync.
|
|
256
|
+
*/
|
|
257
|
+
function reconcile (appRoot, files, managedDirs) {
|
|
258
|
+
for (const dir of managedDirs) {
|
|
259
|
+
for (const rel of walk(join(appRoot, dir))) {
|
|
260
|
+
if (!files.has(join(dir, rel))) rmSync(join(appRoot, dir, rel), { force: true })
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const [rel, content] of files) {
|
|
264
|
+
const path = join(appRoot, rel)
|
|
265
|
+
if (existsSync(path) && readFileSync(path).equals(content)) continue
|
|
266
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
267
|
+
if (existsSync(path)) chmodSync(path, 0o644)
|
|
268
|
+
writeFileSync(path, content)
|
|
269
|
+
chmodSync(path, 0o444)
|
|
270
|
+
}
|
|
271
|
+
for (const dir of managedDirs) pruneEmptyDirs(join(appRoot, dir))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function pruneEmptyDirs (dir) {
|
|
275
|
+
if (!existsSync(dir)) return
|
|
276
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
277
|
+
if (entry.isDirectory()) pruneEmptyDirs(join(dir, entry.name))
|
|
278
|
+
}
|
|
279
|
+
if (readdirSync(dir).length === 0) rmSync(dir, { recursive: true })
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Relative paths of every file under `root`, or [] if it does not exist. */
|
|
283
|
+
function walk (root) {
|
|
284
|
+
if (!existsSync(root) || !statSync(root).isDirectory()) return []
|
|
285
|
+
return readdirSync(root, { recursive: true, withFileTypes: true })
|
|
286
|
+
.filter(e => e.isFile())
|
|
287
|
+
.map(e => relative(root, join(e.parentPath, e.name)))
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizeRoute (route) {
|
|
291
|
+
return route.replace(/^\/+|\/+$/g, '').split('/').join(sep)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function hash (content) {
|
|
295
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16)
|
|
296
|
+
}
|
package/cli/util.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Finds an installed package's directory by walking node_modules upward from `from`, the way Node
|
|
6
|
+
* resolves a bare import. Used instead of require.resolve because several of these packages declare
|
|
7
|
+
* "exports" that do not expose package.json.
|
|
8
|
+
*/
|
|
9
|
+
export function packageDir (name, from) {
|
|
10
|
+
let dir = from
|
|
11
|
+
while (true) {
|
|
12
|
+
const candidate = join(dir, 'node_modules', name)
|
|
13
|
+
if (existsSync(join(candidate, 'package.json'))) return candidate
|
|
14
|
+
const parent = dirname(dir)
|
|
15
|
+
if (parent === dir) throw new Error(`Cannot find package '${name}' from ${from}. Is @reqquest/ui installed?`)
|
|
16
|
+
dir = parent
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readJson (path) {
|
|
21
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Absolute path of a package's executable, from its package.json "bin". */
|
|
25
|
+
export function packageBin (name, from, binName = name.split('/').pop()) {
|
|
26
|
+
const dir = packageDir(name, from)
|
|
27
|
+
const { bin } = readJson(join(dir, 'package.json'))
|
|
28
|
+
return join(dir, typeof bin === 'string' ? bin : bin[binName])
|
|
29
|
+
}
|
package/dist/components.md
CHANGED
|
@@ -5271,10 +5271,6 @@ import TagSkeleton from '@txstate-mws/carbon-svelte/dist/TagSkeleton.svelte'
|
|
|
5271
5271
|
import UserImpersonation from '@txstate-mws/carbon-svelte/dist/UserImpersonation.svelte'
|
|
5272
5272
|
```
|
|
5273
5273
|
|
|
5274
|
-
`@reqquest/ui` has one of these too: `DelayedSkeleton` is compiled into `dist/components/` but is not
|
|
5275
|
-
re-exported from the barrel. The framework's own screens use it via `$lib`; downstream code should
|
|
5276
|
-
register a `loader` on the prompt instead of mounting it directly.
|
|
5277
|
-
|
|
5278
5274
|
### When this file is not enough
|
|
5279
5275
|
|
|
5280
5276
|
- **Props, exactly.** `carbon-svelte` ships unbundled, so
|
package/dist/registry.js
CHANGED
|
@@ -55,7 +55,7 @@ export class UIRegistry {
|
|
|
55
55
|
this.validateReviewSections(program);
|
|
56
56
|
this.userLookup = config.userLookup;
|
|
57
57
|
this.lang = {
|
|
58
|
-
appRequest: config.terminology?.appRequest ??
|
|
58
|
+
appRequest: config.terminology?.appRequest ?? 'Application',
|
|
59
59
|
login: config.terminology?.login ?? 'Login',
|
|
60
60
|
period: config.terminology?.period ?? 'Period'
|
|
61
61
|
};
|
|
@@ -190,6 +190,7 @@ export interface AppRequest {
|
|
|
190
190
|
/** Actions the user can take on this app request. */
|
|
191
191
|
actions: AppRequestActions;
|
|
192
192
|
applicant: AccessUser;
|
|
193
|
+
/** The applications on this request, one per program. Pass programKeys to receive only some of them - a screen that shows one program should not download every program's requirement and prompt tree. */
|
|
193
194
|
applications: Application[];
|
|
194
195
|
/** True when at least one reachable prompt anywhere on this request has been invalidated and must be re-answered. Status and phase are computed from the answers on file, so use this flag to explain that the request is held back awaiting a correction. */
|
|
195
196
|
awaitingCorrection: Scalars['Boolean'];
|
|
@@ -558,7 +559,7 @@ export interface Mutation {
|
|
|
558
559
|
roleDeleteGrant: AccessRoleValidatedResponse;
|
|
559
560
|
roleUpdate: AccessRoleValidatedResponse;
|
|
560
561
|
roleUpdateGrant: AccessRoleValidatedResponse;
|
|
561
|
-
/** Submit the app request. */
|
|
562
|
+
/** Submit the app request. Applications whose program has no reviewer questions (no PREAPPROVAL or APPROVAL requirements) advance on their own into their first blocking workflow stage or to REVIEW_COMPLETE; when nothing is left to review, the review is completed automatically as well. Applications ruled out before submission (PREQUAL or QUALIFICATION) do not hold that back. */
|
|
562
563
|
submitAppRequest: ValidatedAppRequestResponse;
|
|
563
564
|
/** Toggle an existing note's persistent status. */
|
|
564
565
|
togglePersistence: ValidatedNoteResponse;
|
|
@@ -739,6 +740,10 @@ export interface Query {
|
|
|
739
740
|
announcements: Announcement[];
|
|
740
741
|
/** The activity log for this app request. This is a list of actions taken on the app request, such as submission, updating prompts, make an offer, add a note, etc. It will be sorted by the date of the activity in descending order. */
|
|
741
742
|
appRequestActivity: AppRequestActivity[];
|
|
743
|
+
/** The distinct action verbs that appear in the activity log for the associated app request, sorted alphabetically. */
|
|
744
|
+
appRequestActivityActions: Scalars['String'][];
|
|
745
|
+
/** Everyone who appears in the activity log for the associated app request, as the user that performed an activity or as the admin impersonating them. */
|
|
746
|
+
appRequestActivityUsers: AccessUser[];
|
|
742
747
|
appRequestIndexes: IndexCategory[];
|
|
743
748
|
appRequests: AppRequest[];
|
|
744
749
|
applicationMetrics: ApplicationMetric;
|
|
@@ -1138,7 +1143,12 @@ export interface AppRequestGenqlSelection {
|
|
|
1138
1143
|
/** Actions the user can take on this app request. */
|
|
1139
1144
|
actions?: AppRequestActionsGenqlSelection;
|
|
1140
1145
|
applicant?: AccessUserGenqlSelection;
|
|
1141
|
-
applications
|
|
1146
|
+
/** The applications on this request, one per program. Pass programKeys to receive only some of them - a screen that shows one program should not download every program's requirement and prompt tree. */
|
|
1147
|
+
applications?: (ApplicationGenqlSelection & {
|
|
1148
|
+
__args?: {
|
|
1149
|
+
programKeys?: (Scalars['String'][] | null);
|
|
1150
|
+
};
|
|
1151
|
+
});
|
|
1142
1152
|
/** True when at least one reachable prompt anywhere on this request has been invalidated and must be re-answered. Status and phase are computed from the answers on file, so use this flag to explain that the request is held back awaiting a correction. */
|
|
1143
1153
|
awaitingCorrection?: boolean | number;
|
|
1144
1154
|
/** Date that this request was considered closed and no longer editable. If active or re-opened, will be null. If closed again, will be the second closure date. */
|
|
@@ -1782,7 +1792,7 @@ export interface MutationGenqlSelection {
|
|
|
1782
1792
|
validateOnly?: (Scalars['Boolean'] | null);
|
|
1783
1793
|
};
|
|
1784
1794
|
});
|
|
1785
|
-
/** Submit the app request. */
|
|
1795
|
+
/** Submit the app request. Applications whose program has no reviewer questions (no PREAPPROVAL or APPROVAL requirements) advance on their own into their first blocking workflow stage or to REVIEW_COMPLETE; when nothing is left to review, the review is completed automatically as well. Applications ruled out before submission (PREQUAL or QUALIFICATION) do not hold that back. */
|
|
1786
1796
|
submitAppRequest?: (ValidatedAppRequestResponseGenqlSelection & {
|
|
1787
1797
|
__args: {
|
|
1788
1798
|
appRequestId: Scalars['ID'];
|
|
@@ -2078,6 +2088,18 @@ export interface QueryGenqlSelection {
|
|
|
2078
2088
|
paged?: (Pagination | null);
|
|
2079
2089
|
};
|
|
2080
2090
|
});
|
|
2091
|
+
/** The distinct action verbs that appear in the activity log for the associated app request, sorted alphabetically. */
|
|
2092
|
+
appRequestActivityActions?: {
|
|
2093
|
+
__args: {
|
|
2094
|
+
id: Scalars['String'];
|
|
2095
|
+
};
|
|
2096
|
+
};
|
|
2097
|
+
/** Everyone who appears in the activity log for the associated app request, as the user that performed an activity or as the admin impersonating them. */
|
|
2098
|
+
appRequestActivityUsers?: (AccessUserGenqlSelection & {
|
|
2099
|
+
__args: {
|
|
2100
|
+
id: Scalars['String'];
|
|
2101
|
+
};
|
|
2102
|
+
});
|
|
2081
2103
|
appRequestIndexes?: (IndexCategoryGenqlSelection & {
|
|
2082
2104
|
__args?: {
|
|
2083
2105
|
categories?: (Scalars['String'][] | null);
|
|
@@ -388,7 +388,11 @@ type AppRequest {
|
|
|
388
388
|
"""Actions the user can take on this app request."""
|
|
389
389
|
actions: AppRequestActions!
|
|
390
390
|
applicant: AccessUser!
|
|
391
|
-
|
|
391
|
+
|
|
392
|
+
"""
|
|
393
|
+
The applications on this request, one per program. Pass programKeys to receive only some of them - a screen that shows one program should not download every program's requirement and prompt tree.
|
|
394
|
+
"""
|
|
395
|
+
applications(programKeys: [String!]): [Application!]!
|
|
392
396
|
|
|
393
397
|
"""
|
|
394
398
|
True when at least one reachable prompt anywhere on this request has been invalidated and must be re-answered. Status and phase are computed from the answers on file, so use this flag to explain that the request is held back awaiting a correction.
|
|
@@ -1526,7 +1530,9 @@ type Mutation {
|
|
|
1526
1530
|
roleUpdate(role: AccessRoleInput!, roleId: ID!, validateOnly: Boolean): AccessRoleValidatedResponse!
|
|
1527
1531
|
roleUpdateGrant(grant: AccessRoleGrantUpdate!, grantId: ID!, validateOnly: Boolean): AccessRoleValidatedResponse!
|
|
1528
1532
|
|
|
1529
|
-
"""
|
|
1533
|
+
"""
|
|
1534
|
+
Submit the app request. Applications whose program has no reviewer questions (no PREAPPROVAL or APPROVAL requirements) advance on their own into their first blocking workflow stage or to REVIEW_COMPLETE; when nothing is left to review, the review is completed automatically as well. Applications ruled out before submission (PREQUAL or QUALIFICATION) do not hold that back.
|
|
1535
|
+
"""
|
|
1530
1536
|
submitAppRequest(appRequestId: ID!): ValidatedAppRequestResponse!
|
|
1531
1537
|
|
|
1532
1538
|
"""Toggle an existing note's persistent status."""
|
|
@@ -1913,6 +1919,16 @@ type Query {
|
|
|
1913
1919
|
id: String!
|
|
1914
1920
|
paged: Pagination
|
|
1915
1921
|
): [AppRequestActivity!]!
|
|
1922
|
+
|
|
1923
|
+
"""
|
|
1924
|
+
The distinct action verbs that appear in the activity log for the associated app request, sorted alphabetically.
|
|
1925
|
+
"""
|
|
1926
|
+
appRequestActivityActions(id: String!): [String!]!
|
|
1927
|
+
|
|
1928
|
+
"""
|
|
1929
|
+
Everyone who appears in the activity log for the associated app request, as the user that performed an activity or as the admin impersonating them.
|
|
1930
|
+
"""
|
|
1931
|
+
appRequestActivityUsers(id: String!): [AccessUser!]!
|
|
1916
1932
|
appRequestIndexes(
|
|
1917
1933
|
categories: [String!]
|
|
1918
1934
|
|
|
@@ -197,7 +197,9 @@ declare const _default: {
|
|
|
197
197
|
AppRequest: {
|
|
198
198
|
actions: number[];
|
|
199
199
|
applicant: number[];
|
|
200
|
-
applications: number
|
|
200
|
+
applications: (number | {
|
|
201
|
+
programKeys: (string | number)[];
|
|
202
|
+
})[];
|
|
201
203
|
awaitingCorrection: number[];
|
|
202
204
|
closedAt: number[];
|
|
203
205
|
createdAt: number[];
|
|
@@ -804,6 +806,12 @@ declare const _default: {
|
|
|
804
806
|
id: (string | number)[];
|
|
805
807
|
paged: number[];
|
|
806
808
|
})[];
|
|
809
|
+
appRequestActivityActions: (number | {
|
|
810
|
+
id: (string | number)[];
|
|
811
|
+
})[];
|
|
812
|
+
appRequestActivityUsers: (number | {
|
|
813
|
+
id: (string | number)[];
|
|
814
|
+
})[];
|
|
807
815
|
appRequestIndexes: (number | {
|
|
808
816
|
categories: (string | number)[];
|
|
809
817
|
for: number[];
|
|
@@ -103,7 +103,7 @@ export default {
|
|
|
103
103
|
91
|
|
104
104
|
],
|
|
105
105
|
"description": [
|
|
106
|
-
|
|
106
|
+
91
|
|
107
107
|
],
|
|
108
108
|
"label": [
|
|
109
109
|
91
|
|
@@ -290,7 +290,7 @@ export default {
|
|
|
290
290
|
},
|
|
291
291
|
"AccessTag": {
|
|
292
292
|
"description": [
|
|
293
|
-
|
|
293
|
+
91
|
|
294
294
|
],
|
|
295
295
|
"label": [
|
|
296
296
|
91
|
|
@@ -507,7 +507,13 @@ export default {
|
|
|
507
507
|
17
|
|
508
508
|
],
|
|
509
509
|
"applications": [
|
|
510
|
-
37
|
|
510
|
+
37,
|
|
511
|
+
{
|
|
512
|
+
"programKeys": [
|
|
513
|
+
91,
|
|
514
|
+
"[String!]"
|
|
515
|
+
]
|
|
516
|
+
}
|
|
511
517
|
],
|
|
512
518
|
"awaitingCorrection": [
|
|
513
519
|
47
|
|
@@ -851,7 +857,7 @@ export default {
|
|
|
851
857
|
91
|
|
852
858
|
],
|
|
853
859
|
"hiddenIneligiblePreSubmit": [
|
|
854
|
-
|
|
860
|
+
47
|
|
855
861
|
],
|
|
856
862
|
"id": [
|
|
857
863
|
55
|
|
@@ -1939,7 +1945,7 @@ export default {
|
|
|
1939
1945
|
91
|
|
1940
1946
|
],
|
|
1941
1947
|
"configuration": [
|
|
1942
|
-
|
|
1948
|
+
50
|
|
1943
1949
|
],
|
|
1944
1950
|
"eligibilityDescription": [
|
|
1945
1951
|
91
|
|
@@ -2127,6 +2133,24 @@ export default {
|
|
|
2127
2133
|
]
|
|
2128
2134
|
}
|
|
2129
2135
|
],
|
|
2136
|
+
"appRequestActivityActions": [
|
|
2137
|
+
91,
|
|
2138
|
+
{
|
|
2139
|
+
"id": [
|
|
2140
|
+
91,
|
|
2141
|
+
"String!"
|
|
2142
|
+
]
|
|
2143
|
+
}
|
|
2144
|
+
],
|
|
2145
|
+
"appRequestActivityUsers": [
|
|
2146
|
+
17,
|
|
2147
|
+
{
|
|
2148
|
+
"id": [
|
|
2149
|
+
91,
|
|
2150
|
+
"String!"
|
|
2151
|
+
]
|
|
2152
|
+
}
|
|
2153
|
+
],
|
|
2130
2154
|
"appRequestIndexes": [
|
|
2131
2155
|
56,
|
|
2132
2156
|
{
|