Skip to content
Merged
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
17 changes: 17 additions & 0 deletions __tests__/flashlight/compose-template.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,21 @@ describe('renderCompose', () => {
expect(yml).toContain('MYSQL_DATABASE: prestashop');
expect(yml).toMatch(/depends_on:\s*\n\s+mysql:\s*\n\s+condition: service_healthy/);
});

it('does not mount init-scripts by default', () => {
const yml = renderCompose({
psVersion: '9.0.0', port: 8000,
workspace: '/w', containerPath: '/var/www/html',
});
expect(yml).not.toContain('/tmp/init-scripts');
});

it('mounts init-scripts read-only when initScriptsHostPath is provided', () => {
const yml = renderCompose({
psVersion: '9.0.0', port: 8000,
workspace: '/w', containerPath: '/var/www/html',
initScriptsHostPath: '/w/flashlight/init-scripts',
});
expect(yml).toContain('/w/flashlight/init-scripts:/tmp/init-scripts:ro');
});
});
105 changes: 77 additions & 28 deletions __tests__/flashlight/mount.test.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,93 @@
import { resolveMount } from '../../src/flashlight/mount';

describe('resolveMount', () => {
it('module type mounts under modules/', () => {
const r = resolveMount({
composerJson: { name: 'prestaflow/my-module', type: 'prestashop-module' },
workspaceBasename: 'my-module',
describe("mode: 'auto'", () => {
it('module type mounts under modules/', () => {
const r = resolveMount({
composerJson: { name: 'prestaflow/my-module', type: 'prestashop-module' },
workspaceBasename: 'my-module',
mode: 'auto',
});
expect(r.containerPath).toBe('/var/www/html/modules/my-module');
expect(r.warning).toBeUndefined();
});

it('theme type mounts under themes/', () => {
const r = resolveMount({
composerJson: { name: 'prestaflow/my-theme', type: 'prestashop-theme' },
workspaceBasename: 'my-theme',
mode: 'auto',
});
expect(r.containerPath).toBe('/var/www/html/themes/my-theme');
expect(r.warning).toBeUndefined();
});

it('falls back to basename under modules/ when composer.json missing', () => {
const r = resolveMount({ composerJson: null, workspaceBasename: 'weird-repo', mode: 'auto' });
expect(r.containerPath).toBe('/var/www/html/modules/weird-repo');
expect(r.warning).toBeTruthy();
expect(r.warning).toContain('flashlight-mount');
});
expect(r.containerPath).toBe('/var/www/html/modules/my-module');
expect(r.warning).toBeUndefined();
});

it('theme type mounts under themes/', () => {
const r = resolveMount({
composerJson: { name: 'prestaflow/my-theme', type: 'prestashop-theme' },
workspaceBasename: 'my-theme',
it('falls back when type unknown', () => {
const r = resolveMount({
composerJson: { name: 'x/y', type: 'library' },
workspaceBasename: 'y',
mode: 'auto',
});
expect(r.containerPath).toBe('/var/www/html/modules/y');
expect(r.warning).toBeTruthy();
});

it('uses composer name without slash', () => {
const r = resolveMount({
composerJson: { name: 'nosash', type: 'prestashop-module' },
workspaceBasename: 'other',
mode: 'auto',
});
expect(r.containerPath).toBe('/var/www/html/modules/nosash');
});
expect(r.containerPath).toBe('/var/www/html/themes/my-theme');
expect(r.warning).toBeUndefined();
});

it('falls back to basename under modules/ when composer.json missing', () => {
const r = resolveMount({ composerJson: null, workspaceBasename: 'weird-repo' });
expect(r.containerPath).toBe('/var/www/html/modules/weird-repo');
expect(r.warning).toBeTruthy();
describe("mode: 'root'", () => {
it('mounts at /var/www/html regardless of composer.json', () => {
const r = resolveMount({
composerJson: { name: 'x/y', type: 'prestashop-module' },
workspaceBasename: 'y',
mode: 'root',
});
expect(r.containerPath).toBe('/var/www/html');
expect(r.warning).toBeUndefined();
});

it('mounts at /var/www/html even when composer.json is missing', () => {
const r = resolveMount({ composerJson: null, workspaceBasename: 'y', mode: 'root' });
expect(r.containerPath).toBe('/var/www/html');
expect(r.warning).toBeUndefined();
});
});

it('falls back when type unknown', () => {
const r = resolveMount({
composerJson: { name: 'x/y', type: 'library' },
workspaceBasename: 'y',
describe("mode: 'modules' (force)", () => {
it('forces modules/<name> even if composer says theme', () => {
const r = resolveMount({
composerJson: { name: 'x/theme', type: 'prestashop-theme' },
workspaceBasename: 'theme',
mode: 'modules',
});
expect(r.containerPath).toBe('/var/www/html/modules/theme');
expect(r.warning).toBeUndefined();
});
expect(r.containerPath).toBe('/var/www/html/modules/y');
expect(r.warning).toBeTruthy();
});

it('uses composer name without slash', () => {
const r = resolveMount({
composerJson: { name: 'nosash', type: 'prestashop-module' },
workspaceBasename: 'other',
describe("mode: 'themes' (force)", () => {
it('forces themes/<name> even if composer says module', () => {
const r = resolveMount({
composerJson: { name: 'x/mod', type: 'prestashop-module' },
workspaceBasename: 'mod',
mode: 'themes',
});
expect(r.containerPath).toBe('/var/www/html/themes/mod');
expect(r.warning).toBeUndefined();
});
expect(r.containerPath).toBe('/var/www/html/modules/nosash');
});
});
28 changes: 27 additions & 1 deletion __tests__/inputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { parseInputs } from '../src/inputs';
function withInputs(map: Record<string, string>, eventName = 'push', fn: () => void) {
const originals: Record<string, string | undefined> = {};
for (const [k, v] of Object.entries(map)) {
const key = `INPUT_${k.toUpperCase().replace(/-/g, '_')}`;
// Mirror @actions/core.getInput: uppercase the name, replace spaces with '_',
// but keep dashes verbatim — the runner sets env vars with dashes and
// Node's process.env reads them fine.
const key = `INPUT_${k.replace(/ /g, '_').toUpperCase()}`;
originals[key] = process.env[key];
process.env[key] = v;
}
Expand All @@ -28,11 +31,34 @@ describe('parseInputs', () => {
expect(i.suites).toEqual([]);
expect(i.flashlight).toBe(false);
expect(i.psVersion).toBe('latest');
expect(i.flashlightMount).toBe('auto');
expect(i.flashlightInitScripts).toBe('');
expect(i.prComment).toBe(false);
expect(i.uploadArtifacts).toBe(true);
});
});

it('parses flashlight-mount input', () => {
withInputs({ token: 't', 'flashlight-mount': 'root' }, 'push', () => {
expect(parseInputs().flashlightMount).toBe('root');
});
withInputs({ token: 't', 'flashlight-mount': 'THEMES' }, 'push', () => {
expect(parseInputs().flashlightMount).toBe('themes');
});
});

it('throws on invalid flashlight-mount', () => {
withInputs({ token: 't', 'flashlight-mount': 'nope' }, 'push', () => {
expect(() => parseInputs()).toThrow(/flashlight-mount/);
});
});

it('parses flashlight-init-scripts input', () => {
withInputs({ token: 't', 'flashlight-init-scripts': 'flashlight/init' }, 'push', () => {
expect(parseInputs().flashlightInitScripts).toBe('flashlight/init');
});
});

it('parses suites CSV', () => {
withInputs({ token: 't', suites: 'BackOffice, FrontOffice' }, 'push', () => {
expect(parseInputs().suites).toEqual(['BackOffice', 'FrontOffice']);
Expand Down
8 changes: 8 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ inputs:
description: "Flashlight image tag (e.g. 9.0.0, 8.1.7, latest). Ignored when flashlight is false."
required: false
default: 'latest'
flashlight-mount:
description: "Where to mount the workspace inside the Flashlight container. 'auto' (default) picks modules/themes based on composer.json type, or falls back to modules/<repo>. 'root' mounts the workspace at /var/www/html (custom PrestaShop). 'modules' and 'themes' force those locations."
required: false
default: 'auto'
flashlight-init-scripts:
description: "Optional path (absolute or relative to the workspace) to a directory of Flashlight init scripts. Mounted read-only at /tmp/init-scripts. Scripts must be executable and start with a shebang; they run alphabetically at container boot."
required: false
default: ''
pr-comment:
description: "Post/update a summary comment on the pull request. Defaults to true on pull_request events, false otherwise."
required: false
Expand Down
45 changes: 42 additions & 3 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -119963,6 +119963,9 @@ ZipStream.prototype.finalize = function() {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.renderCompose = renderCompose;
function renderCompose(p) {
const extraVolume = p.initScriptsHostPath
? ` - ${p.initScriptsHostPath}:/tmp/init-scripts:ro\n`
: '';
return `services:
prestashop:
image: prestashop/prestashop-flashlight:${p.psVersion}
Expand All @@ -119973,7 +119976,7 @@ function renderCompose(p) {
- "${p.port}:80"
volumes:
- ${p.workspace}:${p.containerPath}
environment:
${extraVolume} environment:
PS_DOMAIN: localhost:${p.port}
DEBUG_MODE: 0
INIT_ON_RESTART: 0
Expand Down Expand Up @@ -120126,15 +120129,25 @@ function nameFrom(cj, fallback) {
return fallback;
}
function resolveMount(p) {
const type = p.composerJson?.type;
if (p.mode === 'root') {
return { containerPath: '/var/www/html' };
}
const name = nameFrom(p.composerJson, p.workspaceBasename);
if (p.mode === 'modules') {
return { containerPath: `/var/www/html/modules/${name}` };
}
if (p.mode === 'themes') {
return { containerPath: `/var/www/html/themes/${name}` };
}
// mode === 'auto' — detect from composer.json type
const type = p.composerJson?.type;
if (type === 'prestashop-module')
return { containerPath: `/var/www/html/modules/${name}` };
if (type === 'prestashop-theme')
return { containerPath: `/var/www/html/themes/${name}` };
return {
containerPath: `/var/www/html/modules/${p.workspaceBasename}`,
warning: `composer.json missing or type unknown — defaulting to /var/www/html/modules/${p.workspaceBasename}`,
warning: `composer.json missing or type unknown — defaulting to /var/www/html/modules/${p.workspaceBasename}. Set flashlight-mount to override.`,
};
}

Expand Down Expand Up @@ -120194,6 +120207,16 @@ function getCsv(name) {
return [];
return raw.split(',').map(s => s.trim()).filter(Boolean);
}
const VALID_MOUNT_MODES = ['auto', 'root', 'modules', 'themes'];
function getMountMode() {
const raw = core.getInput('flashlight-mount').trim().toLowerCase();
if (!raw)
return 'auto';
if (!VALID_MOUNT_MODES.includes(raw)) {
throw new Error(`Input \`flashlight-mount\` must be one of ${VALID_MOUNT_MODES.join(', ')} (got: ${raw})`);
}
return raw;
}
function parseInputs() {
const token = core.getInput('token');
if (!token)
Expand All @@ -120207,6 +120230,8 @@ function parseInputs() {
suites: getCsv('suites'),
flashlight: getBool('flashlight', false),
psVersion: core.getInput('ps-version') || 'latest',
flashlightMount: getMountMode(),
flashlightInitScripts: core.getInput('flashlight-init-scripts').trim(),
prComment: getBool('pr-comment', prCommentDefault),
githubToken: core.getInput('github-token'),
uploadArtifacts: getBool('upload-artifacts', true),
Expand Down Expand Up @@ -120312,15 +120337,29 @@ async function run() {
const mount = (0, mount_1.resolveMount)({
composerJson: cj,
workspaceBasename: path.basename(workspace),
mode: inputs.flashlightMount,
});
if (mount.warning)
core.warning(mount.warning);
core.info(`Mounting workspace at ${mount.containerPath} (mode: ${inputs.flashlightMount})`);
let initScriptsHostPath;
if (inputs.flashlightInitScripts) {
const resolved = path.isAbsolute(inputs.flashlightInitScripts)
? inputs.flashlightInitScripts
: path.join(workspace, inputs.flashlightInitScripts);
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
throw new Error(`flashlight-init-scripts: path does not exist or is not a directory: ${resolved}`);
}
initScriptsHostPath = resolved;
core.info(`Mounting init-scripts from ${resolved} → /tmp/init-scripts (read-only)`);
}
const port = await (0, docker_1.pickPort)([8000, 8001, 8002]);
const composeYaml = (0, compose_template_1.renderCompose)({
psVersion: inputs.psVersion,
port,
workspace,
containerPath: mount.containerPath,
initScriptsHostPath,
});
flashlight = await (0, docker_1.startFlashlight)({ composeYaml, port });
env.PRESTAFLOW_FO_URL = `${flashlight.url}/`;
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion src/flashlight/compose-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ export interface RenderParams {
port: number;
workspace: string;
containerPath: string;
initScriptsHostPath?: string;
}

export function renderCompose(p: RenderParams): string {
const extraVolume = p.initScriptsHostPath
? ` - ${p.initScriptsHostPath}:/tmp/init-scripts:ro\n`
: '';

return `services:
prestashop:
image: prestashop/prestashop-flashlight:${p.psVersion}
Expand All @@ -16,7 +21,7 @@ export function renderCompose(p: RenderParams): string {
- "${p.port}:80"
volumes:
- ${p.workspace}:${p.containerPath}
environment:
${extraVolume} environment:
PS_DOMAIN: localhost:${p.port}
DEBUG_MODE: 0
INIT_ON_RESTART: 0
Expand Down
20 changes: 18 additions & 2 deletions src/flashlight/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ export interface Mount {
warning?: string;
}

export type MountMode = 'auto' | 'root' | 'modules' | 'themes';

export interface ResolveParams {
composerJson: ComposerJson | null;
workspaceBasename: string;
mode: MountMode;
}

function nameFrom(cj: ComposerJson | null, fallback: string): string {
Expand All @@ -20,12 +23,25 @@ function nameFrom(cj: ComposerJson | null, fallback: string): string {
}

export function resolveMount(p: ResolveParams): Mount {
const type = p.composerJson?.type;
if (p.mode === 'root') {
return { containerPath: '/var/www/html' };
}

const name = nameFrom(p.composerJson, p.workspaceBasename);

if (p.mode === 'modules') {
return { containerPath: `/var/www/html/modules/${name}` };
}
if (p.mode === 'themes') {
return { containerPath: `/var/www/html/themes/${name}` };
}

// mode === 'auto' — detect from composer.json type
const type = p.composerJson?.type;
if (type === 'prestashop-module') return { containerPath: `/var/www/html/modules/${name}` };
if (type === 'prestashop-theme') return { containerPath: `/var/www/html/themes/${name}` };
return {
containerPath: `/var/www/html/modules/${p.workspaceBasename}`,
warning: `composer.json missing or type unknown — defaulting to /var/www/html/modules/${p.workspaceBasename}`,
warning: `composer.json missing or type unknown — defaulting to /var/www/html/modules/${p.workspaceBasename}. Set flashlight-mount to override.`,
};
}
Loading
Loading