@reqquest/ui 1.17.0 → 1.19.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 +508 -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 +5 -4
- package/dist/registry.d.ts +17 -0
- package/dist/registry.js +3 -0
- 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
|
@@ -194,6 +194,11 @@ export const uiRegistry = new UIRegistry(config)
|
|
|
194
194
|
`'small'` is about 8 lines at 320px; `'large'` about 32 lines at 800px; `'full'` is a full-screen
|
|
195
195
|
modal, which also **disables autosave in the reviewer UI** and is what supplies `appRequestId` to
|
|
196
196
|
the reviewer form. All three must stay responsive down to 320px.
|
|
197
|
+
- **`reviewerDialogButtonText`** `string` (default `'Review'`) - a reviewer question registered with
|
|
198
|
+
`formMode: 'full'` gets a labeled button on the reviewer screen that opens its form in the
|
|
199
|
+
full-height side dialog. This is that button's text and the dialog's title; name what the reviewer
|
|
200
|
+
will look at, e.g. `'Review course information'`. Automation prompts and applicant prompts keep the
|
|
201
|
+
edit pencil. The global fallback of the same setting lives on `UIConfig`.
|
|
197
202
|
- **`displayMode`** `'small' | 'large'` (default `'small'`) - there is deliberately no `'full'`,
|
|
198
203
|
because the whole prompt list has to print on one page. Use `'large'` together with
|
|
199
204
|
`PromptDisplayGrid` when one prompt collects several question/answer pairs.
|
|
@@ -5271,10 +5276,6 @@ import TagSkeleton from '@txstate-mws/carbon-svelte/dist/TagSkeleton.svelte'
|
|
|
5271
5276
|
import UserImpersonation from '@txstate-mws/carbon-svelte/dist/UserImpersonation.svelte'
|
|
5272
5277
|
```
|
|
5273
5278
|
|
|
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
5279
|
### When this file is not enough
|
|
5279
5280
|
|
|
5280
5281
|
- **Props, exactly.** `carbon-svelte` ships unbundled, so
|
package/dist/registry.d.ts
CHANGED
|
@@ -93,6 +93,15 @@ export interface PromptDefinition {
|
|
|
93
93
|
* Defaults to 'small'.
|
|
94
94
|
*/
|
|
95
95
|
formMode?: 'small' | 'large' | 'full';
|
|
96
|
+
/**
|
|
97
|
+
* Text of the button on the reviewer screen that opens this prompt's form in the full-height side
|
|
98
|
+
* dialog, also used as that dialog's title. Only applies to reviewer questions registered with
|
|
99
|
+
* `formMode: 'full'`; automation prompts and applicant prompts keep the edit pencil. Name what the
|
|
100
|
+
* reviewer will look at, e.g. "Review course information".
|
|
101
|
+
*
|
|
102
|
+
* Falls back to `UIConfig.reviewerDialogButtonText`, then to "Review".
|
|
103
|
+
*/
|
|
104
|
+
reviewerDialogButtonText?: string;
|
|
96
105
|
/**
|
|
97
106
|
* Set this to true to make the prompt visually distinct as something that is meant to
|
|
98
107
|
* be filled in by an automation. If you provide a formComponent, humans with permission
|
|
@@ -307,6 +316,13 @@ export interface UIConfig<PK extends string = PromptKey, RK extends string = Req
|
|
|
307
316
|
* render nothing anywhere and handle the `statusReasons` prop inside your own form components.
|
|
308
317
|
*/
|
|
309
318
|
statusReasonNotifications?: StatusReasonNotificationConfig;
|
|
319
|
+
/**
|
|
320
|
+
* Global layer of `PromptDefinition.reviewerDialogButtonText`: the text of the reviewer screen's button
|
|
321
|
+
* that opens a `formMode: 'full'` reviewer question in the full-height side dialog, and that dialog's
|
|
322
|
+
* title. A prompt's own value
|
|
323
|
+
* overrides this one. Defaults to "Review".
|
|
324
|
+
*/
|
|
325
|
+
reviewerDialogButtonText?: string;
|
|
310
326
|
/**
|
|
311
327
|
* Applicant Review submission page title and subtitle text.
|
|
312
328
|
*/
|
|
@@ -434,6 +450,7 @@ export declare class UIRegistry {
|
|
|
434
450
|
getWord(key: keyof Terminologies, count?: number, inclusive?: boolean): string;
|
|
435
451
|
getPlural(key: keyof Terminologies): string;
|
|
436
452
|
getPrompt(key: string): PromptDefinition | undefined;
|
|
453
|
+
reviewerDialogButtonText(key: string): string;
|
|
437
454
|
protected statusReasonStatusCache: Map<string, Set<RequirementStatus>>;
|
|
438
455
|
statusReasonNotificationStatuses(key: string, audience: StatusReasonAudience): Set<RequirementStatus>;
|
|
439
456
|
getRequirement(key: string): RequirementDefinition | undefined;
|
package/dist/registry.js
CHANGED
|
@@ -104,6 +104,9 @@ export class UIRegistry {
|
|
|
104
104
|
getPrompt(key) {
|
|
105
105
|
return this.warnIfMissing('prompt', key, this.promptMap[key]);
|
|
106
106
|
}
|
|
107
|
+
reviewerDialogButtonText(key) {
|
|
108
|
+
return this.promptMap[key]?.reviewerDialogButtonText ?? this.config.reviewerDialogButtonText ?? 'Review';
|
|
109
|
+
}
|
|
107
110
|
statusReasonStatusCache = new Map();
|
|
108
111
|
// The statuses whose requirement reasons the framework renders below this prompt's edit form for the given
|
|
109
112
|
statusReasonNotificationStatuses(key, audience) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reqquest/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"overrides": {
|
|
5
5
|
"esrap": "2.3.2"
|
|
6
6
|
},
|
|
@@ -11,8 +11,14 @@
|
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"reqquest-ui": "bin/reqquest-ui.js"
|
|
16
|
+
},
|
|
14
17
|
"files": [
|
|
15
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"app",
|
|
20
|
+
"bin",
|
|
21
|
+
"cli"
|
|
16
22
|
],
|
|
17
23
|
"scripts": {
|
|
18
24
|
"prepare": "svelte-kit sync",
|
|
@@ -20,40 +26,48 @@
|
|
|
20
26
|
"build": "vite build",
|
|
21
27
|
"preview": "vite preview",
|
|
22
28
|
"package": "svelte-kit sync && svelte-package",
|
|
23
|
-
"prepublishOnly": "npm run package",
|
|
24
29
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
25
30
|
"check:registry": "node check-registry.js",
|
|
26
31
|
"docs:components": "node scripts/build-components-md.js",
|
|
27
32
|
"docs:components:check": "node scripts/build-components-md.js --check",
|
|
28
|
-
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch"
|
|
33
|
+
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
|
34
|
+
"prepack": "npm run package && node scripts/pack-app.js"
|
|
29
35
|
},
|
|
30
36
|
"peerDependencies": {
|
|
31
|
-
"@sveltejs/kit": "
|
|
32
|
-
"svelte": "
|
|
37
|
+
"@sveltejs/kit": "2.70.3",
|
|
38
|
+
"@sveltejs/vite-plugin-svelte": "6.2.4",
|
|
39
|
+
"svelte": "5.57.2",
|
|
40
|
+
"vite": "7.3.7"
|
|
33
41
|
},
|
|
34
42
|
"dependencies": {
|
|
43
|
+
"@sveltejs/adapter-static": "3.0.10",
|
|
35
44
|
"@txstate-mws/carbon-svelte": "^1.5.5",
|
|
36
45
|
"@txstate-mws/fastify-shared": "^1.1.0",
|
|
37
46
|
"@txstate-mws/svelte-components": "^1.6.11",
|
|
47
|
+
"@txstate-mws/svelte-forms": "2.1.10",
|
|
38
48
|
"@txstate-mws/sveltekit-utils": "^1.0.2",
|
|
49
|
+
"autoprefixer": "10.6.1",
|
|
50
|
+
"carbon-components-svelte": "0.112.2",
|
|
51
|
+
"carbon-icons-svelte": "13.16.0",
|
|
52
|
+
"carbon-preprocess-svelte": "0.11.49",
|
|
39
53
|
"luxon": "^3.5.0",
|
|
40
|
-
"
|
|
54
|
+
"postcss": "8.5.29",
|
|
55
|
+
"svelte-check": "4.7.6",
|
|
56
|
+
"tailwindcss": "3.4.19",
|
|
57
|
+
"txstate-utils": "^1.8.15",
|
|
58
|
+
"typescript": "5.9.3"
|
|
41
59
|
},
|
|
42
60
|
"devDependencies": {
|
|
43
61
|
"@stylistic/eslint-plugin": "^5.0.0",
|
|
44
|
-
"@sveltejs/
|
|
45
|
-
"@sveltejs/kit": "^2.5.2",
|
|
62
|
+
"@sveltejs/kit": "2.70.3",
|
|
46
63
|
"@sveltejs/package": "^2.0.0",
|
|
47
|
-
"@sveltejs/vite-plugin-svelte": "
|
|
64
|
+
"@sveltejs/vite-plugin-svelte": "6.2.4",
|
|
48
65
|
"@types/luxon": "^3.4.2",
|
|
49
66
|
"@types/node": "^24.3.1",
|
|
50
|
-
"carbon-preprocess-svelte": "~0.11.2",
|
|
51
67
|
"eslint-config-love": "^151.0.0",
|
|
52
68
|
"eslint-plugin-svelte": "^3.0.0",
|
|
53
|
-
"svelte": "
|
|
54
|
-
"
|
|
55
|
-
"typescript": "^5.3.3",
|
|
56
|
-
"vite": "^7.0.0"
|
|
69
|
+
"svelte": "5.57.2",
|
|
70
|
+
"vite": "7.3.7"
|
|
57
71
|
},
|
|
58
72
|
"type": "module"
|
|
59
73
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { Loader } from "../registry";
|
|
2
|
-
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
3
|
-
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
4
|
-
$$bindings?: Bindings;
|
|
5
|
-
} & Exports;
|
|
6
|
-
(internal: unknown, props: Props & {
|
|
7
|
-
$$events?: Events;
|
|
8
|
-
$$slots?: Slots;
|
|
9
|
-
}): Exports & {
|
|
10
|
-
$set?: any;
|
|
11
|
-
$on?: any;
|
|
12
|
-
};
|
|
13
|
-
z_$$bindings?: Bindings;
|
|
14
|
-
}
|
|
15
|
-
declare const DelayedSkeleton: $$__sveltets_2_IsomorphicComponent<{
|
|
16
|
-
loader: Loader;
|
|
17
|
-
}, {
|
|
18
|
-
[evt: string]: CustomEvent<any>;
|
|
19
|
-
}, {}, {}, string>;
|
|
20
|
-
type DelayedSkeleton = InstanceType<typeof DelayedSkeleton>;
|
|
21
|
-
export default DelayedSkeleton;
|