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
9 changes: 8 additions & 1 deletion build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ async function build() {
...baseOptions,
outfile: 'dist/index.js',
format: 'esm',
})
}),
esbuild.build({
...baseOptions,
entryPoints: ['src/wizard.ts'],
external: [...baseOptions.external!, '@stencil/cli'],
outfile: 'dist/wizard.js',
format: 'esm',
}),
]);
}

Expand Down
3 changes: 2 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"networkidle",
"outfile",
"tada",
"upvote"
"upvote",
"nypm"
]
}
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"stencil": {
"wizard": "./dist/wizard.js"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./wizard": {
"types": "./dist/wizard.d.ts",
"import": "./dist/wizard.js"
}
},
"files": [
Expand Down Expand Up @@ -51,13 +58,14 @@
},
"peerDependencies": {
"@playwright/test": ">=1.50.0",
"@stencil/core": "^4.0.0 || >=5.0.0-alpha.0 <5.0.0-beta.0"
"@stencil/core": "^4.0.0 || ^5.0.0 || >=5.0.0-alpha.19 <5.0.0-next.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@ionic/prettier-config": "^4.0.0",
"@playwright/test": "^1.61.1",
"@stencil/core": ">=5.0.0-alpha.0 <5.0.0-beta.0",
"@stencil/cli": "^5.0.0 || >=5.0.0-alpha.19 <5.0.0-next.0",
"@stencil/core": "^5.0.0 || >=5.0.0-alpha.19 <5.0.0-next.0",
"@types/eslint": "^9.6.1",
"@types/node": "^24.13.3",
"@typescript-eslint/eslint-plugin": "^8.63.0",
Expand Down
17 changes: 16 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
packages:
- test-projects/wizard-playground
allowBuilds:
'@parcel/watcher': true
esbuild: true
Expand Down
22 changes: 12 additions & 10 deletions src/load-config-meta.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// @ts-ignore - position of type import changed in Stencil 5
import { loadConfig, OutputTargetWww } from '@stencil/core/compiler';
import { findUp } from 'find-up';
import { existsSync } from 'fs';
import { dirname, isAbsolute, join, relative } from 'path';
import { isAbsolute, join, relative } from 'path';

/**
* Common shape for output targets with dir and buildDir properties.
Expand Down Expand Up @@ -39,9 +38,13 @@ export const loadConfigMeta = async (cwd?: string) => {
// This allows for the Playwright config to exist in a different directory than the Stencil config.
const stencilConfigPath = await findUp(['stencil.config.ts', 'stencil.config.js'], { cwd });

// Only load the Stencil config if the user has created one
if (stencilConfigPath && existsSync(stencilConfigPath)) {
const { devServer, fsNamespace, outputTargets } = (await loadConfig({ configPath: stencilConfigPath })).config;
// Always attempt to load a config, even if no stencil.config.ts/.js was found - a v5 "zero-config"
// project has neither, and Stencil's own loadConfig() already knows how to resolve sensible
// defaults for that case (namespace from package.json, "loader-bundle" output target, etc).
const results = await loadConfig({ configPath: stencilConfigPath ?? undefined });

if (results.config && !results.diagnostics.some((d) => d.level === 'error')) {
const { devServer, fsNamespace, outputTargets, rootDir: resolvedRootDir } = results.config;

// Grab a suitable output target for script injection.
// Priority: www > dist (v4) > loader-bundle (v5)
Expand All @@ -52,9 +55,8 @@ export const loadConfigMeta = async (cwd?: string) => {
const loaderBundleTarget = outputTargets.find((o) => (o as OutputTargetWithDir).type === 'loader-bundle') as
OutputTargetWithDir | undefined;

// Use stencil config directory as fallback if devServer.root is not usable
const configDir = dirname(stencilConfigPath);
const rootDir = devServer.root && devServer.root !== '/' ? devServer.root : configDir;
// Use the resolved project rootDir as fallback if devServer.root is not usable
const rootDir = devServer.root && devServer.root !== '/' ? devServer.root : resolvedRootDir;

if (wwwTarget) {
// Get path from dev-server root to www
Expand Down Expand Up @@ -104,8 +106,8 @@ export const loadConfigMeta = async (cwd?: string) => {
stencilNamespace = fsNamespace;
} else {
const msg = stencilConfigPath
? `Unable to find your project's Stencil configuration file, starting from '${stencilConfigPath}'. Falling back to defaults.`
: `No Stencil config file was found matching the glob 'stencil.config.{ts,js}' in the current or parent directories. Falling back to defaults.`;
? `Unable to load your project's Stencil configuration file at '${stencilConfigPath}'. Falling back to defaults.`
: `Unable to resolve a Stencil configuration for this project. Falling back to defaults.`;

console.warn(msg);
}
Expand Down
79 changes: 45 additions & 34 deletions src/test/load-config-meta.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';

import { loadConfigMeta } from '../load-config-meta';

const existsSyncMock = vi.fn();
vi.mock('fs', () => ({
existsSync: () => existsSyncMock(),
}));

const stencilConfig: {
rootDir: string;
fsNamespace: string;
devServer: {
protocol: string;
Expand All @@ -18,6 +14,7 @@ const stencilConfig: {
};
outputTargets: Array<{ type: string; dir: string; buildDir?: string }>;
} = {
rootDir: '/mock-path',
fsNamespace: 'mock-namespace',
devServer: {
protocol: 'http',
Expand All @@ -39,25 +36,30 @@ vi.mock('find-up', () => ({
findUp: () => findUpMock(),
}));

const loadConfigMock = vi.fn();
vi.mock('@stencil/core/compiler', () => ({
loadConfig: () => ({
config: stencilConfig,
}),
loadConfig: (...args: unknown[]) => loadConfigMock(...args),
}));

describe('loadConfigMeta', () => {
beforeEach(() => {
vi.resetAllMocks();
// Most tests just want a successfully-resolved config - override per-test for failure cases.
loadConfigMock.mockResolvedValue({ config: stencilConfig, diagnostics: [] });
});

it('should return defaults if a config does not exist', async () => {
it('should fall back to defaults when the resolved config has an error diagnostic', async () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');
loadConfigMock.mockResolvedValueOnce({
config: null,
diagnostics: [{ level: 'error', messageText: 'boom' }],
});

const configMeta = await loadConfigMeta();

expect(consoleWarnSpy).toHaveBeenCalledWith(
"Unable to find your project's Stencil configuration file, starting from '/mock-path/stencil.config.ts'. Falling back to defaults.",
"Unable to load your project's Stencil configuration file at '/mock-path/stencil.config.ts'. Falling back to defaults.",
);
expect(configMeta).toEqual({
baseURL: 'http://localhost:3333',
Expand All @@ -68,7 +70,6 @@ describe('loadConfigMeta', () => {
});

it('should use the validated Stencil config values', async () => {
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();
Expand All @@ -81,9 +82,41 @@ describe('loadConfigMeta', () => {
});
});

it('should resolve "zero-config" (v5) defaults when no stencil.config.ts/.js is found', async () => {
// No config file on disk - findUp comes back empty, but loadConfig() still resolves
// (namespace from package.json, default "loader-bundle" output target, etc).
findUpMock.mockResolvedValueOnce(undefined);

const configMeta = await loadConfigMeta();

expect(configMeta).toEqual({
baseURL: 'http://localhost:4444',
stencilEntryPath: './www/build/mock-namespace',
stencilNamespace: 'mock-namespace',
webServerUrl: 'http://localhost:4444/status',
});
});

it('should log a warning if no Stencil configuration could be resolved at all', async () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
findUpMock.mockResolvedValueOnce(undefined);
loadConfigMock.mockResolvedValueOnce({ config: null, diagnostics: [] });

const configMeta = await loadConfigMeta();

expect(consoleWarnSpy).toHaveBeenCalledWith(
'Unable to resolve a Stencil configuration for this project. Falling back to defaults.',
);
expect(configMeta).toEqual({
baseURL: 'http://localhost:3333',
stencilEntryPath: './build/app',
stencilNamespace: 'app',
webServerUrl: 'http://localhost:3333/ping',
});
});

it('should log a warning if no supported output target is found', async () => {
stencilConfig.outputTargets = [];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

Expand All @@ -108,7 +141,6 @@ describe('loadConfigMeta', () => {
buildDir: 'custom-build',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();
Expand All @@ -128,7 +160,6 @@ describe('loadConfigMeta', () => {
dir: '/mock-path/dist',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();
Expand All @@ -148,7 +179,6 @@ describe('loadConfigMeta', () => {
dir: '/mock-path/dist/loader-bundle',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();
Expand Down Expand Up @@ -176,7 +206,6 @@ describe('loadConfigMeta', () => {
dir: '/mock-path/dist/loader-bundle',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();
Expand All @@ -200,7 +229,6 @@ describe('loadConfigMeta', () => {
dir: '/mock-path/dist',
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();
Expand All @@ -220,7 +248,6 @@ describe('loadConfigMeta', () => {
dir: 'dist/loader-bundle', // relative path, not absolute
},
];
existsSyncMock.mockReturnValueOnce(true);
findUpMock.mockResolvedValueOnce('/mock-path/stencil.config.ts');

const configMeta = await loadConfigMeta();
Expand All @@ -232,20 +259,4 @@ describe('loadConfigMeta', () => {
webServerUrl: 'http://localhost:4444/status',
});
});

it('should log a warning if no Stencil config path was found', async () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

const configMeta = await loadConfigMeta();

expect(consoleWarnSpy).toHaveBeenCalledWith(
`No Stencil config file was found matching the glob 'stencil.config.{ts,js}' in the current or parent directories. Falling back to defaults.`,
);
expect(configMeta).toEqual({
baseURL: 'http://localhost:3333',
stencilEntryPath: './build/app',
stencilNamespace: 'app',
webServerUrl: 'http://localhost:3333/ping',
});
});
});
Loading
Loading