Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(POC): simpler / more flexible api #181

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

manuel3108
Copy link
Member

@manuel3108 manuel3108 commented Oct 20, 2024

Closes #85

This is a POC to start visualizing stuff and initiate discussions. Besides of some new types & adapting the paraglide adder, I haven't done any implementation. So expect plenty of type errors and failing pipelines.

Up until now I was keeping my mouth shut about #85 as I had many doubts if this was the right way going forward. Also I had a hard time imagining how this would look in a real adder. Now that this is done with this POC it was eye opening how many flexibilities this will bring us & the community. Besides copying some code, there are nearly zero changes that affect the functionality of the adders. Since most of the functionality already exists it should be straight forward to implement this, once we come up with the "final" api. Most of the work probably needs to be done inside the adders, as there is lots of stuff that needs to be moved.

Edit: The diff looks way worse than it actually is. Consider opening the whole file or viewing in VScode

Copy link

changeset-bot bot commented Oct 20, 2024

⚠️ No Changeset found

Latest commit: ba6a78a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

pkg-pr-new bot commented Oct 20, 2024

Open in Stackblitz

pnpm add https://pkg.pr.new/sveltejs/cli/sv@181

commit: ba6a78a

@@ -10,28 +10,12 @@ import {
object,
variables,
exports,
kit
kit as kitJs
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is bad, but works for the moment. This is required since our workspace has a kit property and this import is also named kit

name: () => 'project.inlang/settings.json',
condition: ({ cwd }) => !fs.existsSync(path.join(cwd, 'project.inlang/settings.json')),
content: ({ options, content }) => {
run: ({ api, cwd, options, typescript, kit, dependencyVersion }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
run: ({ api, cwd, options, typescript, kit, dependencyVersion }) => {
run: ({ files, packages, scripts, cwd, options, typescript, kit, dependencyVersion }) => {

Open for discussion: Only one export, or multiple? I had implemented both locally, but decided to go with api as files and scripts only had one property. It might be more future proof, if we export all three things separately.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would probably name them as just sv and file. sv would encapsulate packages and scripts and file would just be for file handling.

but maybe having a unified sv that does it all isn't so bad... hmmm, need to think on it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting approach to just name it sv, i like that.
Although i wouldn't be sure about combining packages and scripts and leaving files separately. I would suggest that we do all or nothing here.

Copy link
Member

@AdrianGonz97 AdrianGonz97 Oct 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the issue with not letting it have it's own namespace is that each method now gets a bit more verbose

ex:

sv.updateFile
sv.createOrUpdateFile
sv.createFile

as opposed to:

file.update
file.createOrUpdate
file.create

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But wouldn't we have exactly the same problem with script and package?
Additionally, package is a reserved keyword in strict mode, which would make it necessary to use the plural or something completely different like dependencies

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to allow sv.file.update() to group it even more. But in that case i would probably start destructuring the object, basically leaving us with an option we already have.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pkg or pkgJson or something would be another option

I haven't made up my mind about splitting vs combining, but do feel we should be consistent. maybe we should ask Rich for feedback after svelte.dev is updated as he's quite good with API design

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thinking on it more, I think a single api via sv should be fine:

sv.file // creates or updates files - see other comment down below
sv.dependency // adds dep
sv.devDependency // adds devDep
sv.execute // runs any scripts
sv.dependsOn // runs another adder(?)

@AdrianGonz97
Copy link
Member

this really does look and feel much nicer!!

there are still things i would love to improve after this, such as using an ast manipulating api like this:

const { script, css, generateCode } = parseSvelte(content);
script.importNamed('../adder-template-demo.txt?raw', { demo: 'Demo' });
css.addClass('foobar', { 'background-color': 'blue' });
return generateCode({ script: script.generateCode(), css: css.generateCode() });

after that, i think we'll be super close to having a library that's ready for community integrations

@manuel3108
Copy link
Member Author

Regarding that bit of code that you removed back to the top: I moved it on purpose because it's only used in that one place and nowhere else, so i thought it was unnecessary polluting global stuff. But whatever works for you, i don't have a strong opinion on that.

const { script, css, generateCode } = parseSvelte(content);
script.importNamed('../adder-template-demo.txt?raw', { demo: 'Demo' });
css.addClass('foobar', { 'background-color': 'blue' });
return generateCode({ script: script.generateCode(), css: css.generateCode() });

I agree, there is lot's of improvement potential in the ast apis as well. Overall i like where this would be going. But as you said, one step at a time.

@AdrianGonz97
Copy link
Member

Regarding that bit of code that you removed back to the top: I moved it on purpose because it's only used in that one place and nowhere else, so i thought it was unnecessary polluting global stuff. But whatever works for you, i don't have a strong opinion on that.

yea, i much prefer keeping actual constants in the top scope, especially in this case so it's not polluting the sections with logic

@AdrianGonz97
Copy link
Member

AdrianGonz97 commented Oct 20, 2024

so one issue with this new api is that we'll no longer be able to determine if an integration has been previously installed as the packages are no longer static values that we can easily examine.

see:

// check if the dependent adder has already been installed
let installed = false;
installed = dependent.packages.every(
// we'll skip the conditions since we don't have any options to supply it
(p) => p.condition !== undefined || !!workspace.dependencyVersion(p.name)
);
if (installed) continue;
// prompt to install the dependent
const install = await p.confirm({
message: `The ${pc.bold(pc.cyan(adder.id))} integration requires ${pc.bold(pc.cyan(depId))} to also be setup. ${pc.green('Include it?')}`
});
if (install !== true) {
p.cancel('Operation cancelled.');
process.exit(1);
}
selectedAdders.push({ type: 'official', adder: dependent });
}

we're accessing the packages field and checking the package.json to see if they're installed.

the lucia integration relies on this to determine whether we should prompt to also install drizzle. this prevents the drizzle adder from being needlessly executed again if drizzle is already present.

not exactly sure how this can be worked around yet, but this is something we definitely needs to solve

@manuel3108
Copy link
Member Author

so one issue with this new api is that we'll no longer be able to determine if an integration has been previously installed as the packages are no longer static values that we can easily examine.

I wasn't aware anymore that we had this in place.

I was initially going to suggest something like this

run: ({ sv, cwd, options, typescript, kit, dependencyVersion }) => {
    if(!!dependencyVersion('drizzle-orm'))
        sv.dependsOn('drizzle)

    // do other file updates
}

That would increase flexibility as well. But this won't work because we will only be able to determine the integrations it depends on while executing the adder. But in general I do think a functional approach like this would be best, maybe by converting our existing dependsOn field into a function and basically doing the same thing as above.

@manuel3108
Copy link
Member Author

I just pushed a rough implementation of the new api (without "dependsOn" detection). This obviously requires more cleanup, but i wanted to make sure we don't miss anything.

Running the paraglide adder in it's current state seems to create the same results as our current implementation. Only had a rough look though, should probably diff the changes later for to make sure this is the case.

const ext = typescript ? 'ts' : 'js';
if (!kit) throw new Error('SvelteKit is required');

sv.dependency('@inlang/paraglide-sveltekit', '^0.11.1');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps it should be called addDependency?

Comment on lines +75 to +76
if (!fs.existsSync(path.join(cwd, 'project.inlang/settings.json'))) {
sv.updateFile('project.inlang/settings.json', (content) => {
Copy link
Member

@benmccann benmccann Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could combine these into a single API that adds the file if it doesn't exist. (maybe createFile, updateFile, and createOrUpdateFile? or updateFile could take an option describing whether to throw an error or overwrite if an existing file is found?)

Suggested change
if (!fs.existsSync(path.join(cwd, 'project.inlang/settings.json'))) {
sv.updateFile('project.inlang/settings.json', (content) => {
if (!fs.existsSync(path.join(cwd, 'project.inlang/settings.json'))) {
sv.updateFile('project.inlang/settings.json', (content) => {

Copy link
Member

@AdrianGonz97 AdrianGonz97 Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think a singular API makes sense (similar to how the name: () => '...' field operates today where it'll only create the file if the returned string is not empty). it could also have a second parameter that indicates the kind:

sv.file('project.inlang/settings.json', (content: string, kind: 'create' | 'update') => {
	if (kind === 'create') {
		// ...
	}
	if (kind === 'update') {
		// ...
	}
});

most of the time though, you wouldn't care about whether the file is created or updated, so it can simply be this:

sv.file('project.inlang/settings.json', (content: string) => {
	// ...
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider a simpler API
3 participants