Skip to content

Commit

Permalink
feat(cli): use new action factory definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
paul-thebaud committed Nov 7, 2023
1 parent 26adcfd commit 174aabc
Show file tree
Hide file tree
Showing 21 changed files with 537 additions and 256 deletions.
7 changes: 5 additions & 2 deletions packages/cli/src/commands/initCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import usePkg from '@foscia/cli/utils/dependencies/usePkg';
import findUp from '@foscia/cli/utils/files/findUp';
import resolvePath from '@foscia/cli/utils/files/resolvePath';
import writeOrPrintFile from '@foscia/cli/utils/files/writeOrPrintFile';
import makeImportsList from '@foscia/cli/utils/imports/makeImportsList';
import findChoice from '@foscia/cli/utils/input/findChoice';
import promptForActionFactoryOptions from '@foscia/cli/utils/input/promptForActionFactoryOptions';
import promptForOverwrite from '@foscia/cli/utils/input/promptForOverwrite';
Expand Down Expand Up @@ -256,8 +257,10 @@ export default {
await promptForOverwrite(factoryPath, 'Action factory was not generated. You can run "foscia make:action" to generate an action factory.');
}

const factoryOptions = await promptForActionFactoryOptions(config, usage);
const factoryContent = renderActionFactory({ config, usage, options: factoryOptions });
const imports = makeImportsList();

const factoryOptions = await promptForActionFactoryOptions(config, imports, usage);
const factoryContent = renderActionFactory({ config, imports, usage, options: factoryOptions });
await writeOrPrintFile('Action factory', factoryPath, factoryContent, config.language, show);
},
} as Command<InitCommandOptions>;
6 changes: 5 additions & 1 deletion packages/cli/src/commands/makeActionFactoryCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Command } from '@foscia/cli/commands/types';
import renderActionFactory from '@foscia/cli/templates/renderActionFactory';
import useConfig from '@foscia/cli/utils/config/useConfig';
import warnMissingDependencies from '@foscia/cli/utils/dependencies/warnMissingDependencies';
import makeImportsList from '@foscia/cli/utils/imports/makeImportsList';
import promptForActionFactoryOptions from '@foscia/cli/utils/input/promptForActionFactoryOptions';
import makeFile from '@foscia/cli/utils/makeFile';
import logSymbols from '@foscia/cli/utils/output/logSymbols';
Expand Down Expand Up @@ -43,10 +44,13 @@ export default {
pc.bold(`\n${logSymbols.foscia} Lets configure your action factory!\n`),
);

const imports = makeImportsList();

return renderActionFactory({
config,
imports,
usage,
options: await promptForActionFactoryOptions(config, usage),
options: await promptForActionFactoryOptions(config, imports, usage),
});
}, show);
},
Expand Down
8 changes: 6 additions & 2 deletions packages/cli/src/commands/makeComposableCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Command } from '@foscia/cli/commands/types';
import renderComposable from '@foscia/cli/templates/renderComposable';
import useConfig from '@foscia/cli/utils/config/useConfig';
import warnMissingDependencies from '@foscia/cli/utils/dependencies/warnMissingDependencies';
import makeImportsList from '@foscia/cli/utils/imports/makeImportsList';
import promptForComposables from '@foscia/cli/utils/input/promptForComposables';
import promptForProperties from '@foscia/cli/utils/input/promptForProperties';
import makeFile from '@foscia/cli/utils/makeFile';
Expand Down Expand Up @@ -43,10 +44,13 @@ export default {
`${logSymbols.foscia} Lets configure your composable's definition (attributes, etc.).\n`,
);

const imports = makeImportsList();

return renderComposable({
config,
composables: await promptForComposables(config),
properties: await promptForProperties(config),
imports,
composables: await promptForComposables(config, imports),
properties: await promptForProperties(config, imports),
});
}, show);
},
Expand Down
8 changes: 6 additions & 2 deletions packages/cli/src/commands/makeModelCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Command } from '@foscia/cli/commands/types';
import renderModel from '@foscia/cli/templates/renderModel';
import useConfig from '@foscia/cli/utils/config/useConfig';
import warnMissingDependencies from '@foscia/cli/utils/dependencies/warnMissingDependencies';
import makeImportsList from '@foscia/cli/utils/imports/makeImportsList';
import promptForComposables from '@foscia/cli/utils/input/promptForComposables';
import promptForProperties from '@foscia/cli/utils/input/promptForProperties';
import makeFile from '@foscia/cli/utils/makeFile';
Expand Down Expand Up @@ -46,12 +47,15 @@ export default {
`${logSymbols.foscia} Lets configure your model's definition (attributes, etc.).\n`,
);

const imports = makeImportsList();

return renderModel({
config,
imports,
className,
typeName,
composables: await promptForComposables(config),
properties: await promptForProperties(config),
composables: await promptForComposables(config, imports),
properties: await promptForProperties(config, imports),
});
}, show);
},
Expand Down
12 changes: 9 additions & 3 deletions packages/cli/src/commands/makeTransformerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Command } from '@foscia/cli/commands/types';
import renderTransformer from '@foscia/cli/templates/renderTransformer';
import useConfig from '@foscia/cli/utils/config/useConfig';
import warnMissingDependencies from '@foscia/cli/utils/dependencies/warnMissingDependencies';
import makeImportsList from '@foscia/cli/utils/imports/makeImportsList';
import makeFile from '@foscia/cli/utils/makeFile';
import { camelCase } from 'lodash-es';
import pc from 'picocolors';
Expand Down Expand Up @@ -35,8 +36,13 @@ export default {

const name = camelCase(args.name);
const fileName = `transformers/${name}`;
await makeFile(config, `Transformer ${name}`, fileName, async () => renderTransformer({
config,
}), show);
await makeFile(config, `Transformer ${name}`, fileName, async () => {
const imports = makeImportsList();

return renderTransformer({
config,
imports,
});
}, show);
},
} as Command<MakeTransformerCommandOptions>;
140 changes: 86 additions & 54 deletions packages/cli/src/templates/renderActionFactory.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,114 @@
import renderExport from '@foscia/cli/templates/renderExport';
import renderImport from '@foscia/cli/templates/renderImport';
import renderImportsList from '@foscia/cli/templates/renderImportsList';
import { CLIConfig } from '@foscia/cli/utils/config/config';
import type { ActionFactoryOptions } from '@foscia/cli/utils/input/promptForActionFactoryOptions';
import { sortBy } from 'lodash-es';
import { ImportsList } from '@foscia/cli/utils/imports/makeImportsList';
import type {
ActionFactoryDependency,
ActionFactoryModelRegistration,
ActionFactoryOptions,
} from '@foscia/cli/utils/input/promptForActionFactoryOptions';
import toIndent from '@foscia/cli/utils/output/toIndent';

type ActionFactoryTemplateData = {
config: CLIConfig;
imports: ImportsList;
usage: CLIConfig['usage'];
options: ActionFactoryOptions;
};

export function renderModelsDefinition({ config, options }: ActionFactoryTemplateData) {
if (options.automaticRegistration === 'import.meta.glob') {
const modelsCast = config.language === 'ts'
? ' as { [k: string]: Model }'
: '';
export function renderModelsRegistration(
{ config, registry }: { config: CLIConfig; registry: ActionFactoryModelRegistration; },
) {
let typeAssertion = '';
if (registry === 'import.meta.glob') {
if (config.language === 'ts') {
typeAssertion = ' as { [k: string]: Model }';
}

return `
const models = Object.values(import.meta.glob('./models/*.${config.language}', {
import: 'default', eager: true,
})${modelsCast});
${toIndent(config, 'import: \'default\', eager: true,')}
})${typeAssertion});
`.trim();
}

if (registry === 'require.context') {
if (config.language === 'ts') {
typeAssertion = ' as Model';
}

return `
const modelsRequireContext = require.context('./models', /\\.${config.language}/);
const models = modelsRequireContext.keys().map(
${toIndent(config, `(key) => modelsRequireContext(key).default${typeAssertion}`)},
);
`.trim();
}

return 'const models = [/* TODO Post, Comment, [...] */];';
}

function renderBlueprintActionFactory({ config, usage, options }: ActionFactoryTemplateData) {
const enableModelFeatures = ['jsonapi', 'jsonrest'].indexOf(usage) !== -1;
const factoryFunction = {
jsonapi: { name: 'makeJsonApi', package: 'jsonapi' },
jsonrest: { name: 'makeJsonRest', package: 'rest' },
http: { name: 'makeHttpClient', package: 'http' },
}[usage];
function renderFactoryOptions(config: CLIConfig, options?: { [K: string]: unknown }) {
const emptyOptions = Object.values(options ?? {}).filter((o) => o !== undefined).length === 0;
if (emptyOptions) {
return '';
}

const factoryConfiguration = {
...(enableModelFeatures ? { models: '____models____' } : {}),
...options.config,
};
const factoryConfigured = Object.values(factoryConfiguration)
.filter((o) => o !== undefined)
.length > 0;
const factoryConfigurationJsonLiteral = factoryConfigured
? JSON.stringify(factoryConfiguration, null, config.tabSize ?? 2).replace(/"([^"]+)":/g, '$1:')
: '';
const factoryConfigurationLiteral = factoryConfigurationJsonLiteral
return JSON.stringify(options, null, (config.tabSize ?? 2))
.replace(/"([^"]+)":/g, '$1:')
.replace(/"____([a-z]+)____"/, '$1')
.replace('models: models,', 'models,');

const blueprintImportStatement = renderImport({
config,
name: `{ ${factoryFunction.name} }`,
from: `@foscia/${factoryFunction.package}`,
});
.replace(/\\"/g, '\\\'')
.replace(/"/g, '\'');
}

const coreImports = [] as string[];
if (enableModelFeatures && options.automaticRegistration) {
coreImports.push('Model');
}
function renderFactoryDependency(
config: CLIConfig,
comment: string,
dependency?: ActionFactoryDependency,
) {
return dependency
? `${comment}\n...${dependency.name}(${renderFactoryOptions(config, dependency.options)}),`
: undefined;
}

const coreImportStatement = coreImports.length
? `\n${renderImport({
config,
name: `{ ${sortBy(coreImports).join(', ')} }`,
from: '@foscia/core',
})}`
export default function renderActionFactory(
{ config, imports, options }: ActionFactoryTemplateData,
) {
const modelsRegistration = options.registry
? `\n${renderModelsRegistration({ config, registry: options.registry })}\n`
: '';

return `
${blueprintImportStatement}${coreImportStatement}
${enableModelFeatures ? `\n${renderModelsDefinition({ config, usage, options })}\n` : ''}
const { action } = ${factoryFunction.name}(${factoryConfigurationLiteral});
const actionFactoryDependencies = [
options.cache
? '// Cache stores already retrieved models\' instances\n// and avoid duplicates records to coexists.\n// If you don\'t care about this feature, you can remove it.\n...makeCache(),'
: undefined,
options.registry
? '// Registry stores a map of type string and models classes.\n// You have this dependency because you\'ve opt-in for it.\n...makeRegistry(models),'
: undefined,
renderFactoryDependency(
config,
'// Deserializer transforms data source\'s raw data to model\'s instances.\n// If you don\'t retrieve models from your data store, you can remove it.',
options.deserializer,
),
renderFactoryDependency(
config,
'// Serializer transforms model\'s instances to your data source\'s format.\n// If you don\'t send models to your data store, you can remove it.',
options.serializer,
),
renderFactoryDependency(
config,
'// Adapter exchanges data with your data source.\n// This is mandatory when using Foscia.',
options.adapter,
),
];

${renderExport({ config, expr: 'action' })}
const actionFactory = `
makeActionFactory({
${actionFactoryDependencies.filter((d) => d).map((d) => toIndent(config, d!)).join('\n')}
})
`.trim();
}

export default function renderActionFactory({ config, usage, options }: ActionFactoryTemplateData) {
return renderBlueprintActionFactory({ config, usage, options });
return `
${renderImportsList({ config, imports })}${modelsRegistration}
${renderExport({ config, expr: actionFactory })}
`.trim();
}
71 changes: 20 additions & 51 deletions packages/cli/src/templates/renderComposable.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,51 @@
import { CLIConfig } from '@foscia/cli/utils/config/config';
import renderComposableForDef from '@foscia/cli/templates/renderComposableForDef';
import renderExport from '@foscia/cli/templates/renderExport';
import renderImport from '@foscia/cli/templates/renderImport';
import renderImportsList from '@foscia/cli/templates/renderImportsList';
import renderPropertyForDef from '@foscia/cli/templates/renderPropertyForDef';
import { CLIConfig } from '@foscia/cli/utils/config/config';
import { ImportsList } from '@foscia/cli/utils/imports/makeImportsList';
import { DefinitionProperty } from '@foscia/cli/utils/input/promptForProperties';
import toIndent from '@foscia/cli/utils/output/toIndent';
import toJoinMultiline from '@foscia/cli/utils/output/toJoinMultiline';
import { MakeProperty, MakeType } from '@foscia/cli/utils/make';
import { sortBy, uniq } from 'lodash-es';
import { uniq } from 'lodash-es';

type ComposableTemplateData = {
config: CLIConfig;
composables: MakeType[];
properties: MakeProperty[];
imports: ImportsList;
composables: string[];
properties: DefinitionProperty[];
};

export function renderFosciaImports(
{ config, properties, name }: { config: CLIConfig, properties: MakeProperty[], name: string },
) {
return renderImport({
config,
name: [name, ...properties.map((p) => p.typology)],
from: '@foscia/core',
});
}

export function renderDefinitionImports(
{ config, types }: { config: CLIConfig, types: (MakeProperty | MakeType)[] },
context: 'models' | 'composables',
) {
return toJoinMultiline(sortBy(uniq(
types
.map((p) => {
const isProperty = 'type' in p;
const type = (isProperty ? p.type : p) as MakeType;

return renderImport({
config,
name: type?.name,
from: type?.from,
typeOnly: isProperty,
context,
});
})
.filter((i) => i) as string[],
)));
}

export function renderDefinition(
{ config, composables, properties }: {
config: CLIConfig;
composables: MakeType[];
properties: MakeProperty[];
composables: string[];
properties: DefinitionProperty[];
},
) {
const definition = (composables.length + properties.length)
? toJoinMultiline(uniq([
? `${uniq([
...composables.map(
(c) => `${toIndent(config)}${renderComposableForDef({ composable: c })}`,
(composable) => toIndent(config, renderComposableForDef({ composable })),
),
...properties.map(
(p) => `${toIndent(config)}${renderPropertyForDef({ property: p })}`,
(property) => toIndent(config, renderPropertyForDef({ property })),
),
]), ',\n')
: `${toIndent(config)}// TODO Write definition.\n`;
]).join(',\n')},`
: `${toIndent(config, '// TODO Write definition.')}`;

return `{\n${definition}}`.trim();
return `{\n${definition}\n}`.trim();
}

export default function renderComposable(
{ config, composables, properties }: ComposableTemplateData,
{ config, imports, composables, properties }: ComposableTemplateData,
) {
const composableDef = renderDefinition({ config, composables, properties });
const composableObject = `makeComposable(${composableDef})`;
const composableTypes = [...composables, ...properties];

imports.add('makeComposable', '@foscia/core');

return `
${renderFosciaImports({ config, properties, name: 'makeComposable' })}
${renderDefinitionImports({ config, types: composableTypes }, 'composables')}
${renderImportsList({ config, imports, context: 'composables' })}
${renderExport({ config, expr: composableObject })}
`.trim();
}
Loading

0 comments on commit 174aabc

Please sign in to comment.