Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/craftcms-cp/src/utilities/api/actionClient.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import axios, {type RawAxiosRequestHeaders} from 'axios';
import {Csrf} from '@src/services/Csrf';
import {ConfigService} from '@src/services/Config';

/**
* @TODO
* Builds an action URL using the runtime-configured action base
* (`Url::actionUrl()`), so the CP trigger isn't hard-coded to `/admin`.
*/
export function getActionUrl(action: string = '') {
return `/admin/actions/${action}`;
return ConfigService.getInstance().getActionUrl(action);
}

/**
Expand All @@ -27,13 +29,15 @@ export function actionHeaders(): RawAxiosRequestHeaders {
return headers;
}

export const actionClient = axios.create({
baseURL: getActionUrl(),
});
export const actionClient = axios.create();

const csrf = new Csrf();

actionClient.interceptors.request.use(async (config) => {
// Resolve the base URL lazily so it reflects the runtime CP trigger. Config
// isn't guaranteed to be initialized when this module is first imported.
config.baseURL = getActionUrl();

// Set X-Requested-With header
config.headers.set('X-Requested-With', 'XMLHttpRequest');

Expand Down
4 changes: 4 additions & 0 deletions resources/js/bootstrap/cp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import SystemMessages from '@/modules/utilities/components/system-messages/SystemMessages.vue';
import DeprecationErrorsToolbar from '@/modules/utilities/components/deprecation-errors/DeprecationErrorsToolbar.vue';
import {setTranslations} from '@craftcms/cp/utilities/translate.ts.mjs';
import {setCpTrigger} from '@/wayfinder/cp-trigger';

Check failure on line 18 in resources/js/bootstrap/cp.ts

View workflow job for this annotation

GitHub Actions / Code Quality / TypeScript

Cannot find module '@/wayfinder/cp-trigger' or its corresponding type declarations.

let bootedCallbacks: Array<(instance: any) => void> = [];
let bootingCallbacks: Array<(instance: any) => void> = [];
Expand Down Expand Up @@ -53,6 +54,9 @@

init() {
config.initialize(this.initialConfig);

// Make Wayfinder-generated route URLs use the runtime-configured CP trigger.
setCpTrigger(config.get('cpTrigger'));
queue.initialize({
runAutomatically: config.get('runQueueAutomatically', true),
enabled: true,
Expand Down
4 changes: 3 additions & 1 deletion resources/js/modules/install/components/InstallingScreen.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import {usePost} from '@/common/composables/useFetch';
import {usePage} from '@inertiajs/vue3';
import Pane from '@/common/components/Pane.vue';
import useCraftData from '@/common/composables/useCraftData';

const {props: pageProps} = usePage();
const {general} = useCraftData();

const props = defineProps<{
data: any;
Expand All @@ -17,7 +19,7 @@
isSuccess,
isLoading,
isError,
} = usePost('/admin/actions/install/install', {
} = usePost(`/${general.cpTrigger}/actions/install/install`, {
onSuccess: () => {
setTimeout(() => {
window.location.href = pageProps.postCpLoginRedirect as string;
Expand Down
6 changes: 4 additions & 2 deletions resources/js/modules/updater/composables/useUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {computed, type ComputedRef, type Ref, ref} from 'vue';
import {t} from '@craftcms/cp';
import axios from 'axios';
import useCraftData from '@/common/composables/useCraftData';

/**
* State returned from updater API endpoints
Expand Down Expand Up @@ -47,6 +48,7 @@ export function useUpdater(
actionPrefix: string,
initialState: UpdaterState
): UseUpdaterReturn {
const {general} = useCraftData();
const state = ref<UpdaterState>({...initialState});
const isLoading = ref(false);

Expand All @@ -62,7 +64,7 @@ export function useUpdater(

try {
response = await axios.post(
`/admin/actions/${actionPrefix}/${action}`,
`/${general.cpTrigger}/actions/${actionPrefix}/${action}`,
{data: state.value.data},
{
headers: {
Expand Down Expand Up @@ -157,7 +159,7 @@ export function useUpdater(
// Try to disable maintenance mode
axios
.post(
`/admin/actions/${actionPrefix}/finish`,
`/${general.cpTrigger}/actions/${actionPrefix}/finish`,
{data: state.value.data},
{
headers: {
Expand Down
7 changes: 6 additions & 1 deletion resources/js/pages/updater/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
type UpdaterState,
useUpdater,
} from '@/modules/updater/composables/useUpdater';
import useCraftData from '@/common/composables/useCraftData';
const {general} = useCraftData();
const props = defineProps<{
title: string;
Expand Down Expand Up @@ -41,7 +44,9 @@
function handleFinish(): void {
setTimeout(() => {
window.location.href =
state.value.returnUrl || props.returnUrl || '/admin/dashboard';
state.value.returnUrl ||
props.returnUrl ||
`/${general.cpTrigger}/dashboard`;
}, 750);
}
Expand Down
58 changes: 58 additions & 0 deletions scripts/wayfinder-cp-trigger.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env node
/**
* Rewrites Wayfinder-generated route modules so their URLs respect the
* runtime-configured CP trigger (`Cms::config()->cpTrigger`) instead of the
* `/admin` prefix baked in at generation time.
*
* Every generated `*.url()` body ends in `+ queryParams(options)` (and `return`
* appears nowhere else in generated files), so the base path can be reliably
* wrapped in `cpUrl(...)` from `resources/js/wayfinder/cp-trigger.ts`.
*
* This runs as a Vite `transform` hook (see `vite.config.js`) rather than a
* post-generation file rewrite: the `@laravel/vite-plugin-wayfinder` plugin
* regenerates these files on the fly, which would clobber any on-disk edits.
* Transforming at import time keeps the generated files pristine while still
* applying to both dev and production builds.
*/

/**
* Wraps a generated route module's URL bases in `cpUrl(...)` and injects the
* import. Returns the transformed source, or `null` when nothing applies (no
* URL builders, or already transformed).
*
* @param {string} code
* @returns {string | null}
*/
export function transformWayfinderSource(code) {
// Nothing to wrap, or already processed.
if (!code.includes(' + queryParams(options)') || code.includes('cpUrl(')) {
return null;
}

// Wrap each url() base path (everything between `return ` and the trailing
// `+ queryParams(options)`) in cpUrl().
const wrapped = code.replace(
/return ([\s\S]*?) \+ queryParams\(options\)/g,
'return cpUrl($1) + queryParams(options)'
);

if (wrapped === code) {
return null;
}

// Add the cpUrl import as a sibling of the existing wayfinder import, reusing
// its (depth-specific) relative module specifier.
return wrapped.replace(
/^(import .*from '(.*\/wayfinder)')$/m,
"$1\nimport { cpUrl } from '$2/cp-trigger'"
);
}

/** True for Wayfinder-generated route module ids that should be transformed. */
export function isWayfinderRouteModule(id) {
const normalized = id.split('?')[0].replace(/\\/g, '/');
return (
/\/resources\/js\/(actions|routes)\//.test(normalized) &&
normalized.endsWith('.ts')
);
}
26 changes: 26 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,34 @@ import {promisify} from 'util';
import vue from '@vitejs/plugin-vue';
import tailwindcss from '@tailwindcss/vite';
import {wayfinder} from '@laravel/vite-plugin-wayfinder';
import {
transformWayfinderSource,
isWayfinderRouteModule,
} from './scripts/wayfinder-cp-trigger.mjs';

const execAsync = promisify(exec);

/**
* Rewrites Wayfinder-generated route URLs to use the runtime CP trigger instead
* of the `/admin` prefix baked in at generation time. Runs at import time so it
* survives the wayfinder() plugin regenerating the files on disk.
*/
function wayfinderCpTrigger() {
return {
name: 'wayfinder-cp-trigger',
enforce: 'pre',
transform(code, id) {
if (!isWayfinderRouteModule(id)) {
return null;
}

const transformed = transformWayfinderSource(code);

return transformed ? {code: transformed, map: null} : null;
},
};
}

const MIME_TYPES = {
'.js': 'application/javascript',
'.mjs': 'application/javascript',
Expand Down Expand Up @@ -151,6 +176,7 @@ export default defineConfig(({mode}) => {
path: 'resources/js',
command: './vendor/bin/testbench wayfinder:generate',
}),
wayfinderCpTrigger(),
vue({
template: {
compilerOptions: {
Expand Down
Loading