-
Notifications
You must be signed in to change notification settings - Fork 124
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
[Theme] Speed up theme synchronization #4362
Conversation
We detected some changes at either packages/*/src or packages/cli-kit/assets/cli-ruby/** and there are no updates in the .changeset. |
Coverage report
Show files with reduced coverage 🔻
Test suite run success1860 tests passing in 845 suites. Report generated by 🧪jest coverage report action from 3ef4cc3 |
const storefrontPassword = await storefrontPasswordPromise | ||
session.storefrontPassword = storefrontPassword | ||
|
||
await renderProgress() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏻 great idea!
How do you feel about a rename to clarify that this is unrelated to the renderLinks
call below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to renderThemeSyncProgress
👍
Differences in type declarationsWe detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:
New type declarationsWe found no new type declarations in this PR Existing type declarationspackages/cli-kit/dist/public/common/version.d.ts@@ -1 +1 @@
-export declare const CLI_KIT_VERSION = "3.66.0";
\ No newline at end of file
+export declare const CLI_KIT_VERSION = "3.65.0";
\ No newline at end of file
packages/cli-kit/dist/public/node/http.d.ts@@ -1,13 +1,12 @@
import FormData from 'form-data';
-import nodeFetch, { RequestInfo, RequestInit } from 'node-fetch';
-export { FetchError, Request } from 'node-fetch';
+import { RequestInfo, RequestInit, Response } from 'node-fetch';
+export { FetchError, Request, Response } from 'node-fetch';
/**
* Create a new FormData object.
*
* @returns A FormData object.
*/
export declare function formData(): FormData;
-export type Response = Awaited<ReturnType<typeof nodeFetch>>;
/**
* An interface that abstracts way node-fetch. When Node has built-in
* support for "fetch" in the standard library, we can drop the node-fetch
packages/cli-kit/dist/public/node/system.d.ts@@ -1,5 +1,8 @@
/// <reference types="node" resolution-mode="require"/>
+/// <reference types="node" resolution-mode="require"/>
+/// <reference types="node" resolution-mode="require"/>
import { AbortSignal } from './abort.js';
+import { ReadStream } from 'tty';
import type { Writable, Readable } from 'stream';
export interface ExecOptions {
cwd?: string;
@@ -45,8 +48,11 @@ export declare function exec(command: string, args: string[], options?: ExecOpti
*/
export declare function sleep(seconds: number): Promise<void>;
/**
- * Check if the standard input and output streams support prompting.
+ * In case an standard input stream is passed check if it supports raw mode. Otherwise default standard input stream
+ * will be used.
*
- * @returns True if the standard input and output streams support prompting.
+ * @param stdin - The standard input stream to check.
+ * @param env - Environmemnt variables.
+ * @returns True in the selected input stream support raw mode.
*/
-export declare function terminalSupportsPrompting(): boolean;
\ No newline at end of file
+export declare function terminalSupportsRawMode(stdin?: ReadStream, env?: NodeJS.ProcessEnv): boolean;
\ No newline at end of file
packages/cli-kit/dist/public/node/themes/factories.d.ts@@ -24,6 +24,7 @@ export interface RemoteBulkUploadResponse {
}
export declare function buildTheme(themeJson?: RemoteThemeResponse): Theme | undefined;
export declare function buildChecksum(asset?: RemoteAssetResponse): Checksum | undefined;
-export declare function buildThemeAsset(asset?: RemoteAssetResponse): ThemeAsset | undefined;
+export declare function buildThemeAsset(asset: undefined): undefined;
+export declare function buildThemeAsset(asset: RemoteAssetResponse): ThemeAsset;
export declare function buildBulkUploadResults(bulkUploadResponse: RemoteBulkUploadResponse[], assets: AssetParams[]): Result[];
export {};
\ No newline at end of file
packages/cli-kit/dist/public/node/themes/types.d.ts@@ -1,8 +1,27 @@
/// <reference types="node" resolution-mode="require"/>
+/// <reference types="node" resolution-mode="require"/>
+import type { Stats } from 'fs';
/**
* {@link Key} represents the unique identifier of a file in a theme.
*/
export type Key = string;
+export type ThemeFSEventName = 'add' | 'change' | 'unlink';
+interface ThemeFSEventCommonPayload {
+ fileKey: Key;
+ onSync: (fn: () => void) => void;
+}
+type ThemeFSEvent = {
+ type: 'unlink';
+ payload: ThemeFSEventCommonPayload;
+} | {
+ type: 'add' | 'change';
+ payload: ThemeFSEventCommonPayload & {
+ onContent: (fn: (content: string) => void) => void;
+ };
+};
+export type ThemeFSEventPayload<T extends ThemeFSEventName = 'add'> = (ThemeFSEvent & {
+ type: T;
+})['payload'];
/**
* Represents a theme on the file system.
*/
@@ -15,12 +34,16 @@ export interface ThemeFileSystem {
* Local theme files.
*/
files: Map<Key, ThemeAsset>;
+ /**
+ * Promise that resolves when all the initial files are found.
+ */
+ ready: () => Promise<void>;
/**
* Removes a file from the local disk and updates the themeFileSystem
*
- * @param assetKey - The key of the file to remove
+ * @param fileKey - The key of the file to remove
*/
- delete: (assetKey: string) => Promise<void>;
+ delete: (fileKey: Key) => Promise<void>;
/**
* Writes a file to the local disk and updates the themeFileSystem
*
@@ -32,9 +55,22 @@ export interface ThemeFileSystem {
* Returns a ThemeAsset representing the file that was read
* Returns undefined if the file does not exist
*
- * @param assetKey - The key of the file to read
+ * @param fileKey - The key of the file to read
*/
- read: (assetKey: string) => Promise<string | Buffer | undefined>;
+ read: (fileKey: Key) => Promise<string | Buffer | undefined>;
+ /**
+ * Gets the stats of a file from the local disk and updates the themeFileSystem
+ * Returns undefined if the file does not exist
+ *
+ * @param fileKey - The key of the file to read
+ */
+ stat: (fileKey: Key) => Promise<Pick<Stats, 'mtime' | 'size'> | undefined>;
+ /**
+ * Add callbacks to run after certain events are fired.
+ */
+ addEventListener: {
+ <T extends ThemeFSEventName>(eventName: T, cb: (params: ThemeFSEventPayload<T>) => void): void;
+ };
}
/**
* Represents a theme.
@@ -117,4 +153,5 @@ export interface Result {
export declare enum Operation {
Delete = "DELETE",
Upload = "UPLOAD"
-}
\ No newline at end of file
+}
+export {};
\ No newline at end of file
|
vi.mock('./storefront-renderer.js') | ||
|
||
// Vitest is resetting this mock between tests due to a global config `mockReset: true`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't get this working either - Please update me if you figure out another way around this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎩 LGTM + code and works as expected 👍🏻
@jamesmengo I've made further changes that speed up the startup a lot. Please give it another check 🙏 |
} | ||
|
||
async function createBatches(files: Checksum[], path: string): Promise<FileBatch[]> { | ||
const fileSizes = await Promise.all(files.map((file) => fileSize(`${path}/${file.key}`))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was not considering that we upload certain assets coded in base64, so I think batching could be wrong sometimes. It now takes the size of the base64 string for those assets. -- Plus it's now synchronous and doesn't need to read file size again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for cleaning this up!
This has been an area of the codebase I was not proud of and had been hoping to revisit for a while so I'm happy to see these improvements :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem at all! This was a tricky thing to reason about, I was lucky to find it 😅
const result = await Promise.race([ | ||
promise, | ||
new Promise((resolve) => setTimeout(() => resolve('timeout'), timeout)), | ||
]) | ||
|
||
if (result === 'timeout') { | ||
addNextCheck() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds a new task when the timeout ends until promise
is resolved. This way we update the title with a progress %
@@ -34,13 +34,21 @@ export async function push(theme: Theme, session: AdminSession, options: PushOpt | |||
const themeChecksums = await fetchChecksums(theme.id, session) | |||
const themeFileSystem = await mountThemeFileSystem(options.path) | |||
|
|||
const results = await uploadTheme(theme, session, themeChecksums, themeFileSystem, options) | |||
const {uploadResults, renderThemeSyncProgress} = await uploadTheme( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't related to the changes in this PR, but this method is hanging / not terminating when I 🎩!
These changes will resolve that issue for us - specifically this commit
Let's make sure that we're able to merge the changes / commit I linked above before the next to avoid breaking push
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the changes to fix this in your links 🤔
In any case, this problem should be fixed here: e3f82ff
Basically, we only defer the delete job if specified in the function, so that the push command can await for everything. Can you check it? 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry - didn't see this. Push and pull are both working properly 👌🏻 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3c363ff
to
e3f82ff
Compare
I've force-pushed to remove the last 2 commits and move them to this follow-up PR: #4370 I think this can be merged as is and will continue in that other PR after also mergin James' changes into main branch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this PR, @frandiox! This is awesome 🔥 It's 5x faster in my tests.
I've left one concern that might make the upload process a bit slower, but it would still be faster than the version we have on main
. Still, I believe it's worth handling that concern to ensure we keep the uploader stable. Please let me know your thoughts about that :)
Thanks again for the amazing work!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, @frandiox! 🚀
I think this speeds up the dev command startup by a big margin: