@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.
Files changed (105) hide show
  1. package/app/env.template +5 -0
  2. package/app/runtime/apply-env.sh +10 -0
  3. package/app/runtime/nginx.conf +37 -0
  4. package/app/src/app.css +21 -0
  5. package/app/src/app.d.ts +13 -0
  6. package/app/src/app.html +12 -0
  7. package/app/src/internal/api.ts +1707 -0
  8. package/app/src/internal/appreq-utils.ts +33 -0
  9. package/app/src/internal/components/AnnouncementBanner.svelte +47 -0
  10. package/app/src/internal/components/AppRequestCard.svelte +173 -0
  11. package/app/src/internal/components/ApplicantOptOutModal.svelte +78 -0
  12. package/app/src/internal/components/ApplicantProgramList.svelte +276 -0
  13. package/app/src/internal/components/ApplicantProgramListTooltip.svelte +44 -0
  14. package/app/src/internal/components/ApplicantPromptPage.svelte +136 -0
  15. package/app/src/internal/components/ApplicantPromptSkeleton.svelte +12 -0
  16. package/app/src/internal/components/ApplicationDetailsView.svelte +369 -0
  17. package/app/src/internal/components/ButtonLoadingIcon.svelte +28 -0
  18. package/{dist → app/src/internal}/components/DelayedSkeleton.svelte +2 -2
  19. package/app/src/internal/components/FieldNestedMultiselect.svelte +270 -0
  20. package/app/src/internal/components/IntroPanel.svelte +41 -0
  21. package/app/src/internal/components/MissingDefinitionNotification.svelte +20 -0
  22. package/app/src/internal/components/PeriodPanel.svelte +125 -0
  23. package/app/src/internal/components/ProgramStatusCell.svelte +134 -0
  24. package/app/src/internal/components/RenderDisplayComponent.svelte +42 -0
  25. package/app/src/internal/components/ReviewerActions.svelte +171 -0
  26. package/app/src/internal/components/ReviewerQuestions.svelte +444 -0
  27. package/app/src/internal/components/StatusMessageList.svelte +54 -0
  28. package/app/src/internal/components/SystemDeterminationNotice.svelte +37 -0
  29. package/app/src/internal/components/TabLinks.svelte +265 -0
  30. package/app/src/internal/components/WarningIconYellow.svelte +22 -0
  31. package/app/src/internal/components/index.ts +19 -0
  32. package/app/src/internal/csv.ts +30 -0
  33. package/app/src/internal/index.ts +8 -0
  34. package/app/src/internal/prompt-save-queue.ts +91 -0
  35. package/app/src/internal/prompt-utils.ts +40 -0
  36. package/app/src/internal/status-utils.ts +503 -0
  37. package/app/src/internal/stores/IStateStore.ts +5 -0
  38. package/app/src/internal/util.ts +27 -0
  39. package/app/src/routes/+error.svelte +4 -0
  40. package/app/src/routes/+layout.svelte +134 -0
  41. package/app/src/routes/+layout.ts +18 -0
  42. package/app/src/routes/+page.ts +14 -0
  43. package/app/src/routes/announcement/+page.svelte +164 -0
  44. package/app/src/routes/announcement/+page.ts +16 -0
  45. package/app/src/routes/dashboards/applicant/+page.svelte +494 -0
  46. package/app/src/routes/dashboards/applicant/+page.ts +95 -0
  47. package/app/src/routes/dashboards/reviewer/+page.svelte +143 -0
  48. package/app/src/routes/dashboards/reviewer/+page.ts +43 -0
  49. package/app/src/routes/periods/+page.svelte +158 -0
  50. package/app/src/routes/periods/+page.ts +13 -0
  51. package/app/src/routes/periods/[id]/configure/+page.svelte +150 -0
  52. package/app/src/routes/periods/[id]/configure/+page.ts +10 -0
  53. package/app/src/routes/requests/+page.svelte +246 -0
  54. package/app/src/routes/requests/+page.ts +24 -0
  55. package/app/src/routes/requests/[id]/+layout.ts +11 -0
  56. package/app/src/routes/requests/[id]/accept/+layout.svelte +80 -0
  57. package/app/src/routes/requests/[id]/accept/+layout.ts +12 -0
  58. package/app/src/routes/requests/[id]/accept/+page.ts +13 -0
  59. package/app/src/routes/requests/[id]/accept/[promptId]/+page.svelte +7 -0
  60. package/app/src/routes/requests/[id]/accept/[promptId]/+page.ts +10 -0
  61. package/app/src/routes/requests/[id]/accept/review/+page.svelte +87 -0
  62. package/app/src/routes/requests/[id]/apply/+layout.svelte +181 -0
  63. package/app/src/routes/requests/[id]/apply/+layout.ts +12 -0
  64. package/app/src/routes/requests/[id]/apply/+page.ts +14 -0
  65. package/app/src/routes/requests/[id]/apply/ProgressNavContainer.svelte +42 -0
  66. package/app/src/routes/requests/[id]/apply/[promptId]/+page.svelte +23 -0
  67. package/app/src/routes/requests/[id]/apply/[promptId]/+page.ts +11 -0
  68. package/app/src/routes/requests/[id]/apply/programs/+page.svelte +71 -0
  69. package/app/src/routes/requests/[id]/apply/review/+page.svelte +102 -0
  70. package/app/src/routes/requests/[id]/approve/+layout.svelte +114 -0
  71. package/app/src/routes/requests/[id]/approve/+page.ts +14 -0
  72. package/app/src/routes/requests/[id]/approve/ApproveLayout.svelte +164 -0
  73. package/app/src/routes/requests/[id]/approve/[programKey]/+page.svelte +396 -0
  74. package/app/src/routes/requests/[id]/approve/[programKey]/+page.ts +15 -0
  75. package/app/src/routes/requests/[id]/approve/activity/+page.svelte +66 -0
  76. package/app/src/routes/requests/[id]/approve/activity/+page.ts +12 -0
  77. package/app/src/routes/requests/[id]/approve/export/+page.ts +12 -0
  78. package/app/src/routes/requests/[id]/approve/export/+page@[id].svelte +41 -0
  79. package/app/src/routes/requests/[id]/export/+page.svelte +32 -0
  80. package/app/src/routes/requests/[id]/export/+page.ts +12 -0
  81. package/app/src/routes/roles/+layout.svelte +16 -0
  82. package/app/src/routes/roles/+layout.ts +14 -0
  83. package/app/src/routes/roles/+page.svelte +225 -0
  84. package/app/src/routes/roles/+page.ts +13 -0
  85. package/app/src/routes/roles/[id]/+layout.ts +10 -0
  86. package/app/src/routes/roles/[id]/+page.svelte +335 -0
  87. package/app/src/routes/roles/[id]/+page.ts +10 -0
  88. package/app/src/routes/roles/[id]/ControlWithTooltip.svelte +15 -0
  89. package/app/src/routes/roles/[id]/TagsWithTooltip.svelte +42 -0
  90. package/app/src/routes/roles/users/+layout.ts +13 -0
  91. package/app/src/routes/roles/users/+page.svelte +46 -0
  92. package/app/src/routes/roles/users/+page.ts +90 -0
  93. package/app/static/favicon.png +0 -0
  94. package/bin/reqquest-ui.js +117 -0
  95. package/cli/config.js +105 -0
  96. package/cli/sync.js +296 -0
  97. package/cli/util.js +29 -0
  98. package/dist/components.md +0 -4
  99. package/dist/registry.js +1 -1
  100. package/dist/typed-client/schema.d.ts +25 -3
  101. package/dist/typed-client/schema.graphql +18 -2
  102. package/dist/typed-client/types.d.ts +9 -1
  103. package/dist/typed-client/types.js +29 -5
  104. package/package.json +29 -15
  105. 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
+ }
@@ -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 ?? (Object.keys(this.programMap).length > 1 ? 'App Request' : 'Application'),
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?: ApplicationGenqlSelection;
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
- applications: [Application!]!
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
- """Submit the app request."""
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
- 89
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
- 89
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
- 45
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
- 48
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
  {