Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b4b2043
feat(core): add `queryAs` enhancer
paul-thebaud Sep 5, 2024
cc67b02
feat: rework to factories functions and improve docs
paul-thebaud Nov 26, 2024
fada8ca
feat: rework to factories functions and improve docs
paul-thebaud Jan 8, 2025
b3ee8d1
feat(core): add action middlewares
paul-thebaud Jan 9, 2025
6398e28
feat(cli): provide a better and automated integration with nuxt
paul-thebaud Jan 9, 2025
3cb68ae
feat(core): add by ID destroy action
paul-thebaud Jan 11, 2025
de70b1d
chore: refactor and improve docs
paul-thebaud Jan 11, 2025
a26ae8c
fix(deserializer): existence state of related records when destroying
paul-thebaud Jan 11, 2025
06f1035
fix(core): only manage instance state for write action if not done
paul-thebaud Jan 11, 2025
ead2ea0
feat(core): store instance instead of model inside snapshots
paul-thebaud Jan 11, 2025
57dc43b
chore: rename new action middlewares enhancers
paul-thebaud Jan 13, 2025
4d2c92a
feat(http): add http middlewares to replace transformers
paul-thebaud Jan 13, 2025
aaadb1f
feat(core): change transformers to a foscia object
paul-thebaud Jan 13, 2025
bddd783
feat(core): change cache refs to factories
paul-thebaud Jan 15, 2025
ea0f44f
feat(core): add output configuration to logger
paul-thebaud Jan 15, 2025
8858e1b
fix(shared): default env to production when detection failed
paul-thebaud Jan 15, 2025
03cf4c4
feat(core): add new model props factories signatures
paul-thebaud Jan 15, 2025
e7dae1c
docs: fix cache refs factories docs
paul-thebaud Jan 16, 2025
137d670
chore: upgrade dependencies
paul-thebaud Jan 16, 2025
7d12233
docs: fix broken links
paul-thebaud Jan 16, 2025
1972404
chore: change param name in `replaceActionMiddlewares` docs
paul-thebaud Jan 18, 2025
886ae19
feat(core): improve snapshots using deep relations state locking
paul-thebaud Jan 18, 2025
0a9b073
docs: improve models and actions docs
paul-thebaud Jan 18, 2025
52b2112
feat(core): use snapshots inside serializers instead of instances
paul-thebaud Jan 18, 2025
d4f336d
chore: fix docs broken links
paul-thebaud Jan 18, 2025
db9694b
chore: refactor logger
paul-thebaud Jan 18, 2025
a55603a
feat: add relation inverse attachment during deserialization
paul-thebaud Jan 28, 2025
883dcc8
docs: improve docs
paul-thebaud Jan 28, 2025
511e37e
feat: remove config chained modifier on relations factory
paul-thebaud Jan 28, 2025
0c8c2b8
feat(core): add `path` chained modifier to relation
paul-thebaud Jan 28, 2025
9be8ce3
feat(core): action factory variadic calls and more flexible composables
paul-thebaud Jan 29, 2025
e41220f
chore: use another install method for corepack
paul-thebaud Feb 8, 2025
daa6a24
feat(core): model assembled property
paul-thebaud Feb 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ To add a new package, you must add a new corresponding repository
inside the `packages` folder.

You must also define multiple files
(like inside [`packages/core`](packages/core)):
(like inside [`packages/core`](https://github.com/foscia-dev/foscia/tree/main/packages/core)):

- `src/index.ts`: contains all exported modules
- `.release-it.json` manage release and dependencies update.
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM node:20-alpine as dependencies

RUN apk update && apk add --no-cache zip git curl

RUN corepack enable
RUN npm install -g corepack@latest
RUN corepack use pnpm@latest

COPY entrypoint.sh /usr/local/bin/docker-entrypoint.sh
Expand Down
40 changes: 20 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,37 @@
"prepare": "husky"
},
"devDependencies": {
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@commitlint/cli": "^19.6.1",
"@commitlint/config-conventional": "^19.6.0",
"@release-it/bumper": "^6.0.1",
"@release-it/conventional-changelog": "^8.0.1",
"@rollup/plugin-commonjs": "^26.0.1",
"@release-it/conventional-changelog": "^8.0.2",
"@rollup/plugin-commonjs": "^26.0.3",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-node-resolve": "^15.3.1",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.6",
"@types/node": "^18.19.39",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"@types/node": "^18.19.70",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitest/coverage-istanbul": "^1.6.0",
"ansi-colors": "^4.1.3",
"concurrently": "^8.2.2",
"eslint": "^8.57.0",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^18.0.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"execa": "^9.3.0",
"husky": "^9.0.11",
"jsdom": "^24.1.0",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-import": "^2.31.0",
"execa": "^9.5.2",
"husky": "^9.1.7",
"jsdom": "^24.1.3",
"minimist": "^1.2.8",
"ora": "^8.0.1",
"release-it": "^17.4.0",
"rimraf": "^5.0.7",
"rollup": "^4.18.0",
"ora": "^8.1.1",
"release-it": "^17.11.0",
"rimraf": "^5.0.10",
"rollup": "^4.30.1",
"tsc-alias": "^1.8.10",
"tslib": "^2.6.3",
"typescript": "^5.5.2",
"tslib": "^2.8.1",
"typescript": "^5.7.3",
"vitest": "^1.6.0"
},
"engines": {
Expand Down
46 changes: 27 additions & 19 deletions packages/cli/src/commands/initCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import makeCommander from '@foscia/cli/utils/cli/makeCommander';
import makeUsageExamples from '@foscia/cli/utils/cli/makeUsageExamples';
import output from '@foscia/cli/utils/cli/output';
import {
AppFramework,
AppLanguage,
AppModules,
AppPackageManager,
Expand Down Expand Up @@ -141,7 +142,7 @@ async function resolveEnvironment(options: InitCommandOptions) {
return { packageManager, language, modules };
}

async function guessPathAndFramework(): Promise<[string] | [string, 'nuxt']> {
async function guessPathAndFramework(): Promise<[string, undefined] | [string, 'nuxt']> {
if (
await pathExists(resolve('nuxt.config.ts'))
|| await pathExists(resolve('nuxt.config.js'))
Expand All @@ -159,6 +160,7 @@ async function guessPathAndFramework(): Promise<[string] | [string, 'nuxt']> {
await pathExists(resolve('resources/ts'))
? 'resources/ts/data'
: 'resources/js/data',
undefined,
];
}

Expand All @@ -167,10 +169,11 @@ async function guessPathAndFramework(): Promise<[string] | [string, 'nuxt']> {
await pathExists(resolve('assets/ts'))
? 'assets/ts/data'
: 'assets/js/data',
undefined,
];
}

return ['src/data'];
return ['src/data', undefined];
}

function guessAlias(path: string) {
Expand Down Expand Up @@ -240,9 +243,23 @@ export async function runInitCommand(
],
}));

const [defaultPath, framework] = await guessPathAndFramework();
if (framework) {
output.success(`${c.bold('detected framework:')} ${c.cyan(framework)}`);
let framework: AppFramework | undefined;

const [defaultPath, detectedFramework] = await guessPathAndFramework();
if (detectedFramework) {
output.success(`${c.bold('detected framework:')} ${c.cyan(detectedFramework)}`);

const frameworkIntegrate = await promptConfirm({
name: 'integrate',
message: 'would you like to integrate it?',
initial: true,
});
if (frameworkIntegrate) {
framework = detectedFramework;
} else {
output.info('ok, you integrate it later using the following command, but it might requires additional configuration:');
output.instruct(`foscia integrate ${detectedFramework}\n`);
}
}

const filesPath = normalize(path?.length ? path : await promptText({
Expand All @@ -259,14 +276,15 @@ export async function runInitCommand(
packageManager,
language,
modules,
framework,
path: filesPath,
alias: alias || undefined,
tabSize: 2,
};

setLifecycleConfig(config);

const missingDependencies = await checkMissingDependencies(config.usage);
const missingDependencies = await checkMissingDependencies(config.usage, config.framework);
await installDependencies(config, missingDependencies, { show });

const configContent = `${JSON.stringify(config, null, 2)}\n`;
Expand Down Expand Up @@ -305,20 +323,10 @@ export async function runInitCommand(
output.instruct('foscia make action\n');
}

if (framework) {
output.step(`${framework} integration`);
if (config.framework) {
output.step(`${config.framework} integration`);

const frameworkIntegrate = await promptConfirm({
name: 'integrate',
message: `we detected you use ${framework}, would you like to integrate it?`,
initial: true,
});
if (frameworkIntegrate) {
await runNuxtCommand({ payloadPlugin: 'fosciaPayloadPlugin', show, force });
} else {
output.info('ok, integrate it later using:');
output.instruct(`foscia integrate ${framework}\n`);
}
await runNuxtCommand({ payloadPlugin: 'fosciaPayloadPlugin', show, force });
}
}

Expand Down
9 changes: 8 additions & 1 deletion packages/cli/src/commands/integrate/nuxtCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import payloadPluginCommand, {
} from '@foscia/cli/commands/integrate/nuxt/payloadPluginCommand';
import { ConfigurableOptions, useConfigOption } from '@foscia/cli/composables/useConfig';
import { ForceableOptions, useForceOption } from '@foscia/cli/composables/useForce';
import { ShowableOptions, useShowOption } from '@foscia/cli/composables/useShow';
import { ShowableOptions, useShow, useShowOption } from '@foscia/cli/composables/useShow';
import makeCommander from '@foscia/cli/utils/cli/makeCommander';
import output from '@foscia/cli/utils/cli/output';

export type NuxtCommandOptions =
& { payloadPlugin: string; pluginsDirectory?: string; }
Expand All @@ -13,12 +14,18 @@ export type NuxtCommandOptions =
& ForceableOptions;

export async function runNuxtCommand(options: NuxtCommandOptions) {
const show = useShow(options);

await runPayloadPluginCommand(options.payloadPlugin, {
directory: options.pluginsDirectory,
show: options.show,
force: options.force,
config: options.config,
});

if (!show) {
output.info('integration with Nuxt is ready, you should now ensure that you adapter configuration contains `fetch: ofetch.native` option.');
}
}

export default function nuxtCommand() {
Expand Down
8 changes: 6 additions & 2 deletions packages/cli/src/commands/make/makeActionCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export async function runMakeActionCommand(
const show = useShow(options);
const force = useForce(options);
const usage = await useUsage(options, () => config.usage);
const { framework } = config;

await warnMissingDependencies(config);

Expand All @@ -41,8 +42,11 @@ export async function runMakeActionCommand(
return renderAction({
config,
imports,
usage,
options: await promptForActionFactoryOptions(config, imports, { usage, show, force }),
options: await promptForActionFactoryOptions(
config,
imports,
{ usage, framework, show, force },
),
});
}, { show, force });
}
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/templates/concerns/renderImportsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function renderEsmImports(from: string, imports: ImportItem[], isTypeOnly: boole
}

function renderCommonJSImports(from: string, imports: ImportItem[]) {
return `const ${renderImportsNames(imports)} = require('${from})';`;
return `const ${renderImportsNames(imports)} = require('${from}');`;
}

function renderGroupedImports(
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/templates/make/renderAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import toIndent from '@foscia/cli/utils/output/toIndent';
type ActionFactoryTemplateData = {
config: CLIConfig;
imports: ImportsList;
usage: CLIConfig['usage'];
options: ActionFactoryOptions;
};

Expand All @@ -22,6 +21,7 @@ function renderFactoryOptions(config: CLIConfig, options?: { [K: string]: unknow
}

return JSON.stringify(options, null, (config.tabSize ?? 2))
.replace(/: "!raw!([^"]+)"/g, ': $1')
.replace(/"([^"]+)":/g, '$1:')
.replace(/\\"/g, '\\\'')
.replace(/"/g, '\'');
Expand Down
13 changes: 6 additions & 7 deletions packages/cli/src/templates/make/renderEnhancer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type EnhancerTemplateData = {
export default function renderEnhancer(
{ config, imports, functionName }: EnhancerTemplateData,
) {
imports.add('context', '@foscia/core');
imports.add('makeEnhancer', '@foscia/core');

const functionGeneric = config.language === 'ts' ? '<C extends {}>' : '';
const paramTyping = config.language === 'ts' ? ': Action<C>' : '';
Expand All @@ -22,12 +22,11 @@ export default function renderEnhancer(
}

const enhancer = `
function ${functionName}${functionGeneric}() {
${toIndent(config, `return (action${paramTyping}) => {`)}
${toIndent(config, '// TODO Write enhancer logic.', 2)}
${toIndent(config, 'return action.use(context({}))', 2)}
${toIndent(config, '};')}
}
makeEnhancer('${functionName}', ${functionGeneric}(
${toIndent(config, '// TODO Add enhancer parameters here.', 1)}
) => async (action${paramTyping}) => action.use(
${toIndent(config, '// TODO Use other enhancers here, such as `context`.', 1)}
))
`.trim();

return `
Expand Down
12 changes: 7 additions & 5 deletions packages/cli/src/templates/make/renderRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ type RunnerTemplateData = {
export default function renderRunner(
{ config, imports, functionName }: RunnerTemplateData,
) {
imports.add('makeRunner', '@foscia/core');

const functionGeneric = config.language === 'ts' ? '<C extends {}>' : '';
const paramTyping = config.language === 'ts' ? ': Action<C>' : '';
if (config.language === 'ts') {
imports.add('Action', '@foscia/core');
}

const runner = `
function ${functionName}${functionGeneric}() {
${toIndent(config, `return (action${paramTyping}) => {`)}
${toIndent(config, '// TODO Write runner logic.', 2)}
${toIndent(config, '};')}
}
makeRunner('${functionName}', ${functionGeneric}(
${toIndent(config, '// TODO Add runner parameters here.', 1)}
) => async (action${paramTyping}) => action.run(
${toIndent(config, '// TODO Use other enhancers and one runner here, such as `one`.', 1)}
))
`.trim();

return `
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/utils/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
export type AppPackageManager = typeof CONFIG_PACKAGE_MANAGERS[number]['value'];
export type AppLanguage = typeof CONFIG_LANGUAGES[number]['value'];
export type AppModules = typeof CONFIG_MODULES[number]['value'];
export type AppFramework = typeof CONFIG_FRAMEWORKS[number]['value'];
export type AppUsage = typeof CONFIG_USAGES[number]['value'];

export type CLIConfig = {
path: string;
alias?: string;
tabSize?: number;
framework?: AppFramework;
packageManager: AppPackageManager;
language: AppLanguage;
modules: AppModules;
Expand Down Expand Up @@ -56,6 +58,14 @@ export const CONFIG_MODULES = [
},
] as const;

export const CONFIG_FRAMEWORKS = [
{
name: 'Nuxt',
value: 'nuxt',
packages: ['ofetch'],
},
] as const;

export const CONFIG_USAGES = [
{
name: 'consuming a JSON:API',
Expand Down
40 changes: 31 additions & 9 deletions packages/cli/src/utils/config/validateConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
CLIConfig,
CLIConfig, CONFIG_FRAMEWORKS,
CONFIG_LANGUAGES,
CONFIG_MODULES,
CONFIG_PACKAGE_MANAGERS,
Expand All @@ -20,17 +20,39 @@ export default function validateConfig(config: object) {
validateRequired(value) !== true || (typeof value === 'number' && value >= 0) || 'value must be a string'
);
const validateIn = <T extends unknown[]>(values: T) => (value: unknown) => (
values.some((v) => value === v) || `value must match one of: ${values.join(', ')}.`
validateRequired(value) !== true || values.some((v) => value === v) || `value must match one of: ${values.join(', ')}.`
);

const errors = Object.entries({
usage: [validateIn(CONFIG_USAGES.map(({ value }) => value))],
packageManager: [validateIn(CONFIG_PACKAGE_MANAGERS.map(({ value }) => value))],
language: [validateIn(CONFIG_LANGUAGES.map(({ value }) => value))],
modules: [validateIn(CONFIG_MODULES.map(({ value }) => value))],
path: [validateRequired, validateString],
alias: [validateString],
tabSize: [validateUnsignedInt],
usage: [
validateRequired,
validateIn(CONFIG_USAGES.map(({ value }) => value)),
],
packageManager: [
validateRequired,
validateIn(CONFIG_PACKAGE_MANAGERS.map(({ value }) => value)),
],
language: [
validateRequired,
validateIn(CONFIG_LANGUAGES.map(({ value }) => value)),
],
modules: [
validateRequired,
validateIn(CONFIG_MODULES.map(({ value }) => value)),
],
framework: [
validateIn(CONFIG_FRAMEWORKS.map(({ value }) => value)),
],
path: [
validateRequired,
validateString,
],
alias: [
validateString,
],
tabSize: [
validateUnsignedInt,
],
}).reduce((messages, [key, rules]) => {
const value = config[key as keyof typeof config];
let message = true as true | string;
Expand Down
Loading