@plasmicapp/cli 0.1.162

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/.eslintrc.js +61 -0
  2. package/.idea/cli.iml +11 -0
  3. package/.idea/misc.xml +6 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/README +16 -0
  7. package/README.internal +46 -0
  8. package/README.md +17 -0
  9. package/build.sh +8 -0
  10. package/dist/__mocks__/api.d.ts +16 -0
  11. package/dist/__mocks__/api.js +297 -0
  12. package/dist/__tests__/code-utils-spec.d.ts +1 -0
  13. package/dist/__tests__/code-utils-spec.js +838 -0
  14. package/dist/__tests__/ftue-spec.d.ts +1 -0
  15. package/dist/__tests__/ftue-spec.js +39 -0
  16. package/dist/__tests__/project-api-token-spec.d.ts +1 -0
  17. package/dist/__tests__/project-api-token-spec.js +147 -0
  18. package/dist/__tests__/versioned-sync-spec.d.ts +1 -0
  19. package/dist/__tests__/versioned-sync-spec.js +145 -0
  20. package/dist/actions/auth.d.ts +8 -0
  21. package/dist/actions/auth.js +47 -0
  22. package/dist/actions/fix-imports.d.ts +4 -0
  23. package/dist/actions/fix-imports.js +25 -0
  24. package/dist/actions/init.d.ts +62 -0
  25. package/dist/actions/init.js +460 -0
  26. package/dist/actions/project-token.d.ts +6 -0
  27. package/dist/actions/project-token.js +42 -0
  28. package/dist/actions/sync-components.d.ts +10 -0
  29. package/dist/actions/sync-components.js +242 -0
  30. package/dist/actions/sync-global-variants.d.ts +3 -0
  31. package/dist/actions/sync-global-variants.js +89 -0
  32. package/dist/actions/sync-icons.d.ts +7 -0
  33. package/dist/actions/sync-icons.js +92 -0
  34. package/dist/actions/sync-images.d.ts +6 -0
  35. package/dist/actions/sync-images.js +137 -0
  36. package/dist/actions/sync-styles.d.ts +3 -0
  37. package/dist/actions/sync-styles.js +58 -0
  38. package/dist/actions/sync.d.ts +25 -0
  39. package/dist/actions/sync.js +417 -0
  40. package/dist/actions/upload-bundle.d.ts +15 -0
  41. package/dist/actions/upload-bundle.js +28 -0
  42. package/dist/actions/watch.d.ts +14 -0
  43. package/dist/actions/watch.js +90 -0
  44. package/dist/api.d.ts +182 -0
  45. package/dist/api.js +202 -0
  46. package/dist/deps.d.ts +2 -0
  47. package/dist/deps.js +20 -0
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.js +247 -0
  50. package/dist/lib.d.ts +10 -0
  51. package/dist/lib.js +23 -0
  52. package/dist/migrations/0.1.110-fileLocks.d.ts +2 -0
  53. package/dist/migrations/0.1.110-fileLocks.js +15 -0
  54. package/dist/migrations/0.1.143-ensureImportModuleType.d.ts +2 -0
  55. package/dist/migrations/0.1.143-ensureImportModuleType.js +12 -0
  56. package/dist/migrations/0.1.146-addReactRuntime.d.ts +2 -0
  57. package/dist/migrations/0.1.146-addReactRuntime.js +10 -0
  58. package/dist/migrations/0.1.27-migrateInit.d.ts +1 -0
  59. package/dist/migrations/0.1.27-migrateInit.js +8 -0
  60. package/dist/migrations/0.1.28-tsToTsx.d.ts +3 -0
  61. package/dist/migrations/0.1.28-tsToTsx.js +33 -0
  62. package/dist/migrations/0.1.31-ensureProjectIcons.d.ts +2 -0
  63. package/dist/migrations/0.1.31-ensureProjectIcons.js +12 -0
  64. package/dist/migrations/0.1.42-ensureVersion.d.ts +2 -0
  65. package/dist/migrations/0.1.42-ensureVersion.js +12 -0
  66. package/dist/migrations/0.1.57-ensureJsBundleThemes.d.ts +2 -0
  67. package/dist/migrations/0.1.57-ensureJsBundleThemes.js +12 -0
  68. package/dist/migrations/0.1.64-imageFiles.d.ts +2 -0
  69. package/dist/migrations/0.1.64-imageFiles.js +17 -0
  70. package/dist/migrations/0.1.95-componentType.d.ts +2 -0
  71. package/dist/migrations/0.1.95-componentType.js +16 -0
  72. package/dist/migrations/migrations.d.ts +10 -0
  73. package/dist/migrations/migrations.js +119 -0
  74. package/dist/plasmic.schema.json +463 -0
  75. package/dist/test-common/fixtures.d.ts +13 -0
  76. package/dist/test-common/fixtures.js +165 -0
  77. package/dist/tsconfig-transform.json +68 -0
  78. package/dist/utils/auth-utils.d.ts +31 -0
  79. package/dist/utils/auth-utils.js +236 -0
  80. package/dist/utils/checksum.d.ts +4 -0
  81. package/dist/utils/checksum.js +63 -0
  82. package/dist/utils/code-utils.d.ts +46 -0
  83. package/dist/utils/code-utils.js +457 -0
  84. package/dist/utils/config-utils.d.ts +271 -0
  85. package/dist/utils/config-utils.js +178 -0
  86. package/dist/utils/envdetect.d.ts +4 -0
  87. package/dist/utils/envdetect.js +42 -0
  88. package/dist/utils/error.d.ts +14 -0
  89. package/dist/utils/error.js +42 -0
  90. package/dist/utils/file-utils.d.ts +71 -0
  91. package/dist/utils/file-utils.js +433 -0
  92. package/dist/utils/get-context.d.ts +40 -0
  93. package/dist/utils/get-context.js +339 -0
  94. package/dist/utils/help.d.ts +2 -0
  95. package/dist/utils/help.js +56 -0
  96. package/dist/utils/lang-utils.d.ts +10 -0
  97. package/dist/utils/lang-utils.js +52 -0
  98. package/dist/utils/npm-utils.d.ts +28 -0
  99. package/dist/utils/npm-utils.js +215 -0
  100. package/dist/utils/prompts.d.ts +6 -0
  101. package/dist/utils/prompts.js +23 -0
  102. package/dist/utils/resolve-utils.d.ts +13 -0
  103. package/dist/utils/resolve-utils.js +198 -0
  104. package/dist/utils/semver.d.ts +34 -0
  105. package/dist/utils/semver.js +61 -0
  106. package/dist/utils/test-utils.d.ts +22 -0
  107. package/dist/utils/test-utils.js +106 -0
  108. package/dist/utils/user-utils.d.ts +7 -0
  109. package/dist/utils/user-utils.js +48 -0
  110. package/jest.config.js +6 -0
  111. package/package.json +80 -0
  112. package/src/__mocks__/api.ts +394 -0
  113. package/src/__tests__/code-utils-spec.ts +881 -0
  114. package/src/__tests__/ftue-spec.ts +43 -0
  115. package/src/__tests__/project-api-token-spec.ts +208 -0
  116. package/src/__tests__/versioned-sync-spec.ts +176 -0
  117. package/src/actions/auth.ts +43 -0
  118. package/src/actions/fix-imports.ts +13 -0
  119. package/src/actions/init.ts +638 -0
  120. package/src/actions/project-token.ts +36 -0
  121. package/src/actions/sync-components.ts +405 -0
  122. package/src/actions/sync-global-variants.ts +129 -0
  123. package/src/actions/sync-icons.ts +135 -0
  124. package/src/actions/sync-images.ts +191 -0
  125. package/src/actions/sync-styles.ts +71 -0
  126. package/src/actions/sync.ts +747 -0
  127. package/src/actions/upload-bundle.ts +38 -0
  128. package/src/actions/watch.ts +95 -0
  129. package/src/api.ts +407 -0
  130. package/src/deps.ts +18 -0
  131. package/src/index.ts +300 -0
  132. package/src/lib.ts +10 -0
  133. package/src/migrations/0.1.110-fileLocks.ts +16 -0
  134. package/src/migrations/0.1.146-addReactRuntime.ts +8 -0
  135. package/src/migrations/0.1.27-migrateInit.ts +4 -0
  136. package/src/migrations/0.1.28-tsToTsx.ts +37 -0
  137. package/src/migrations/0.1.31-ensureProjectIcons.ts +10 -0
  138. package/src/migrations/0.1.42-ensureVersion.ts +10 -0
  139. package/src/migrations/0.1.57-ensureJsBundleThemes.ts +10 -0
  140. package/src/migrations/0.1.64-imageFiles.ts +15 -0
  141. package/src/migrations/0.1.95-componentType.ts +14 -0
  142. package/src/migrations/migrations.ts +147 -0
  143. package/src/test-common/fixtures.ts +178 -0
  144. package/src/utils/auth-utils.ts +276 -0
  145. package/src/utils/checksum.ts +106 -0
  146. package/src/utils/code-utils.ts +656 -0
  147. package/src/utils/config-utils.ts +551 -0
  148. package/src/utils/envdetect.ts +39 -0
  149. package/src/utils/error.ts +36 -0
  150. package/src/utils/file-utils.ts +526 -0
  151. package/src/utils/get-context.ts +451 -0
  152. package/src/utils/help.ts +75 -0
  153. package/src/utils/lang-utils.ts +52 -0
  154. package/src/utils/npm-utils.ts +223 -0
  155. package/src/utils/prompts.ts +22 -0
  156. package/src/utils/resolve-utils.ts +245 -0
  157. package/src/utils/semver.ts +67 -0
  158. package/src/utils/test-utils.ts +116 -0
  159. package/src/utils/user-utils.ts +37 -0
  160. package/testData/fixImports_plasmic.json +66 -0
  161. package/tsconfig-transform.json +68 -0
  162. package/tsconfig.json +67 -0
@@ -0,0 +1,747 @@
1
+ import chalk from "chalk";
2
+ import { spawnSync } from "child_process";
3
+ import L from "lodash";
4
+ import path from "upath";
5
+ import { CommonArgs } from "..";
6
+ import {
7
+ ChecksumBundle,
8
+ CodeComponentMeta,
9
+ ComponentBundle,
10
+ ProjectIdAndToken,
11
+ ProjectMetaBundle,
12
+ StyleConfigResponse,
13
+ } from "../api";
14
+ import { logger } from "../deps";
15
+ import { getChecksums } from "../utils/checksum";
16
+ import {
17
+ ComponentUpdateSummary,
18
+ fixAllImportStatements,
19
+ formatAsLocal,
20
+ maybeConvertTsxToJsx,
21
+ mkFixImportContext,
22
+ replaceImports,
23
+ } from "../utils/code-utils";
24
+ import {
25
+ CONFIG_FILE_NAME,
26
+ createProjectConfig,
27
+ getOrAddProjectConfig,
28
+ getOrAddProjectLock,
29
+ LOADER_CONFIG_FILE_NAME,
30
+ PlasmicContext,
31
+ PlasmicLoaderConfig,
32
+ updateConfig,
33
+ } from "../utils/config-utils";
34
+ import { HandledError } from "../utils/error";
35
+ import {
36
+ assertAllPathsInRootDir,
37
+ defaultResourcePath,
38
+ existsBuffered,
39
+ readFileText,
40
+ renameFile,
41
+ stripExtension,
42
+ withBufferedFs,
43
+ writeFileContent,
44
+ writeFileText,
45
+ } from "../utils/file-utils";
46
+ import { generateMetadata, getContext, Metadata } from "../utils/get-context";
47
+ import { printFirstSyncInfo } from "../utils/help";
48
+ import { ensure, tuple } from "../utils/lang-utils";
49
+ import {
50
+ findInstalledVersion,
51
+ getCliVersion,
52
+ installCommand,
53
+ installUpgrade,
54
+ isCliGloballyInstalled,
55
+ } from "../utils/npm-utils";
56
+ import { checkVersionResolution } from "../utils/resolve-utils";
57
+ import * as semver from "../utils/semver";
58
+ import { confirmWithUser } from "../utils/user-utils";
59
+ import {
60
+ ComponentPendingMerge,
61
+ syncProjectComponents,
62
+ } from "./sync-components";
63
+ import { syncGlobalVariants } from "./sync-global-variants";
64
+ import { syncProjectIconAssets } from "./sync-icons";
65
+ import { syncProjectImageAssets } from "./sync-images";
66
+ import { upsertStyleTokens } from "./sync-styles";
67
+
68
+ export interface SyncArgs extends CommonArgs {
69
+ projects: readonly string[];
70
+ forceOverwrite: boolean;
71
+ newComponentScheme?: "blackbox" | "direct";
72
+ appendJsxOnMissingBase?: boolean;
73
+ yes?: boolean;
74
+ force?: boolean;
75
+ nonRecursive?: boolean;
76
+ skipUpgradeCheck?: boolean;
77
+ ignorePostSync?: boolean;
78
+ quiet?: boolean;
79
+ metadata?: string;
80
+ allFiles?: boolean;
81
+ loaderConfig?: string;
82
+ }
83
+
84
+ async function ensureRequiredPackages(
85
+ context: PlasmicContext,
86
+ baseDir: string,
87
+ yes?: boolean
88
+ ) {
89
+ const requireds = await context.api.requiredPackages();
90
+
91
+ const confirmInstall = async (
92
+ pkg: string,
93
+ requiredVersion: string,
94
+ opts: { global: boolean; dev: boolean }
95
+ ) => {
96
+ let success = false;
97
+ const command = installCommand(pkg, baseDir, opts);
98
+ const upgrade = await confirmWithUser(
99
+ `A more recent version of ${pkg} >=${requiredVersion} is required. Would you like to upgrade via "${command}"?`,
100
+ yes
101
+ );
102
+ if (upgrade) {
103
+ success = installUpgrade(pkg, baseDir, opts);
104
+ } else {
105
+ success = false;
106
+ }
107
+
108
+ if (!success) {
109
+ throw new HandledError(`Upgrading ${pkg} is required to continue.`);
110
+ }
111
+ };
112
+
113
+ const cliVersion = getCliVersion();
114
+ if (!cliVersion || semver.gt(requireds["@plasmicapp/cli"], cliVersion)) {
115
+ const isGlobal = isCliGloballyInstalled(context.rootDir);
116
+ await confirmInstall("@plasmicapp/cli", requireds["@plasmicapp/cli"], {
117
+ global: isGlobal,
118
+ dev: true,
119
+ });
120
+
121
+ logger.info(
122
+ chalk.bold("@plasmicapp/cli has been upgraded; please try again!")
123
+ );
124
+
125
+ // Exit so the user can run again with the new cli
126
+ throw new HandledError();
127
+ }
128
+
129
+ const reactWebVersion = findInstalledVersion(
130
+ context,
131
+ "@plasmicapp/react-web"
132
+ );
133
+ if (
134
+ !reactWebVersion ||
135
+ semver.gt(requireds["@plasmicapp/react-web"], reactWebVersion)
136
+ ) {
137
+ await confirmInstall(
138
+ "@plasmicapp/react-web",
139
+ requireds["@plasmicapp/react-web"],
140
+ { global: false, dev: false }
141
+ );
142
+ }
143
+
144
+ if (context.config.code.reactRuntime === "automatic") {
145
+ // Using automatic runtime requires installing the @plasmicapp/react-web-runtime package
146
+ const runtimeVersion = findInstalledVersion(
147
+ context,
148
+ "@plasmicapp/react-web-runtime"
149
+ );
150
+ if (
151
+ !runtimeVersion ||
152
+ semver.gt(requireds["@plasmicapp/react-web-runtime"], runtimeVersion)
153
+ ) {
154
+ await confirmInstall(
155
+ "@plasmicapp/react-web-runtime",
156
+ requireds["@plasmicapp/react-web-runtime"],
157
+ { global: false, dev: false }
158
+ );
159
+ }
160
+ }
161
+ }
162
+
163
+ function getLoaderConfigPath(opts: SyncArgs) {
164
+ return opts.loaderConfig || LOADER_CONFIG_FILE_NAME;
165
+ }
166
+
167
+ function maybeReadLoaderConfig(opts: SyncArgs): Partial<PlasmicLoaderConfig> {
168
+ const path = getLoaderConfigPath(opts);
169
+ if (!existsBuffered(path)) {
170
+ return {};
171
+ }
172
+ return JSON.parse(readFileText(path!));
173
+ }
174
+
175
+ function writeLoaderConfig(
176
+ opts: SyncArgs,
177
+ config: Partial<PlasmicLoaderConfig>
178
+ ) {
179
+ const loaderConfigPath = getLoaderConfigPath(opts);
180
+
181
+ writeFileText(
182
+ loaderConfigPath,
183
+ formatAsLocal(JSON.stringify(config), loaderConfigPath, opts.baseDir)
184
+ );
185
+ }
186
+
187
+ /**
188
+ * Sync will always try to sync down a set of components that are version-consistent among specified projects.
189
+ * (we only allow 1 version per projectId).
190
+ * NOTE: the repo/plasmic.json might include projects with conflicting versions in its dependency tree.
191
+ * We leave it to the user to sync all projects if they want us to catch/prevent this.
192
+ * @param opts
193
+ */
194
+ export async function sync(
195
+ opts: SyncArgs,
196
+ metadataDefaults?: Metadata
197
+ ): Promise<void> {
198
+ // Initially allow for a missing auth. Only require an auth once we need to fetch new or updated API tokens for any
199
+ // projects.
200
+
201
+ if (!opts.baseDir) opts.baseDir = process.cwd();
202
+ const baseDir = opts.baseDir;
203
+ let context = await getContext(opts, { enableSkipAuth: true });
204
+
205
+ const isFirstRun = context.config.projects.length === 0;
206
+
207
+ if (!opts.skipUpgradeCheck) {
208
+ await ensureRequiredPackages(context, opts.baseDir, opts.yes);
209
+ }
210
+
211
+ fixFileExtension(context);
212
+ assertAllPathsInRootDir(context);
213
+
214
+ const loaderConfig: Partial<PlasmicLoaderConfig> = process.env.PLASMIC_LOADER
215
+ ? maybeReadLoaderConfig(opts)
216
+ : {};
217
+
218
+ const projectIdToToken = new Map(
219
+ [...context.config.projects, ...(loaderConfig?.projects ?? [])]
220
+ .filter((p) => p.projectApiToken)
221
+ .map((p) => tuple(p.projectId, p.projectApiToken))
222
+ );
223
+
224
+ // Resolve what will be synced
225
+ const projectConfigMap = L.keyBy(context.config.projects, (p) => p.projectId);
226
+ const projectWithVersion = opts.projects.map((p) => {
227
+ const [projectIdToken, versionRange] = p.split("@");
228
+ const [projectId, projectApiToken] = projectIdToken.split(":");
229
+ return {
230
+ projectId,
231
+ versionRange:
232
+ versionRange || projectConfigMap[projectId]?.version || "latest",
233
+ componentIdOrNames: undefined, // Get all components!
234
+ projectApiToken: projectApiToken || projectIdToToken.get(projectId),
235
+ };
236
+ });
237
+
238
+ const projectSyncParams = projectWithVersion.length
239
+ ? projectWithVersion
240
+ : context.config.projects.map((p) => ({
241
+ projectId: p.projectId,
242
+ versionRange: p.version,
243
+ componentIdOrNames: undefined, // Get all components!
244
+ projectApiToken: p.projectApiToken,
245
+ }));
246
+
247
+ // Short-circuit if nothing to sync
248
+ if (projectSyncParams.length === 0) {
249
+ throw new HandledError(
250
+ "Don't know which projects to sync. Please specify via --projects"
251
+ );
252
+ }
253
+
254
+ // If there are any missing projectApiTokens, reload the context, this time requiring auth, so that we can fetch the
255
+ // projectApiTokens from the server (as a user that has permission to do so).
256
+ if (projectSyncParams.some((p) => !p.projectApiToken)) {
257
+ try {
258
+ context = await getContext(opts);
259
+ } catch (e) {
260
+ if (e.message.includes("Unable to authenticate Plasmic")) {
261
+ const configFileName = process.env.PLASMIC_LOADER
262
+ ? LOADER_CONFIG_FILE_NAME
263
+ : CONFIG_FILE_NAME;
264
+ throw new HandledError(
265
+ `Unable to authenticate Plasmic. Please run 'plasmic auth' or check the projectApiTokens in your ${configFileName}, and try again.`
266
+ );
267
+ }
268
+ }
269
+ }
270
+
271
+ // Pass just the root IDs and tokens that we do have for the resolve call. (In reality it doesn't need any of this
272
+ // because it only consults the projectApiToken within projectSyncParams; we could just attach [].)
273
+ context.api.attachProjectIdsAndTokens(
274
+ projectSyncParams.flatMap((p) =>
275
+ p.projectApiToken
276
+ ? [{ projectId: p.projectId, projectApiToken: p.projectApiToken }]
277
+ : []
278
+ )
279
+ );
280
+
281
+ const versionResolution = await context.api.resolveSync(
282
+ projectSyncParams,
283
+ true // we always want to get dependency data
284
+ );
285
+
286
+ // Make sure the resolution is compatible with plasmic.json and plasmic.lock
287
+ const projectsToSync = await checkVersionResolution(
288
+ versionResolution,
289
+ context,
290
+ opts
291
+ );
292
+ if (projectsToSync.length <= 0) {
293
+ logger.info(
294
+ "Your projects are up-to-date with respect to your specified version ranges. Nothing to sync."
295
+ );
296
+ return;
297
+ }
298
+ const summary = new Map<string, ComponentUpdateSummary>();
299
+ const pendingMerge = new Array<ComponentPendingMerge>();
300
+
301
+ // The resolveSync call returns the project API tokens for all relevant projects (sources and dependencies).
302
+ // resolveSync is what does this because it's what is computing all concrete versions to sync, and the dependency
303
+ // graph can change with any version. Subsequent API calls require the exact API tokens, not to redo this work on each
304
+ // call. Only resolveSync accepts just the API tokens for the root projects.
305
+ //
306
+ // We shouldn't simply use projectsToSync, because this list excludes up-to-date projects, but syncing a dependent
307
+ // project still requires tokens to the dependencies.
308
+ const projectIdsAndTokens = [
309
+ ...versionResolution.projects,
310
+ ...versionResolution.dependencies,
311
+ ].map((p) => L.pick(p, "projectId", "projectApiToken"));
312
+
313
+ context.api.attachProjectIdsAndTokens(projectIdsAndTokens);
314
+
315
+ // Perform the actual sync
316
+ await withBufferedFs(async () => {
317
+ // Sync in sequence (no parallelism)
318
+ // going in reverse to get leaves of the dependency tree first
319
+ for (const projectMeta of projectsToSync) {
320
+ await syncProject(
321
+ context,
322
+ opts,
323
+ projectIdsAndTokens,
324
+ projectMeta.projectId,
325
+ projectMeta.componentIds,
326
+ projectMeta.version,
327
+ projectMeta.dependencies,
328
+ summary,
329
+ pendingMerge,
330
+ metadataDefaults
331
+ );
332
+ }
333
+
334
+ // Materialize scheme into each component config.
335
+ context.config.projects.forEach((p) =>
336
+ p.components.forEach((c) => {
337
+ if (c.type === "managed" && !c.scheme) {
338
+ c.scheme = context.config.code.scheme;
339
+ }
340
+ })
341
+ );
342
+
343
+ await syncStyleConfig(
344
+ context,
345
+ await context.api.genStyleConfig(context.config.style)
346
+ );
347
+
348
+ // Update project version if specified and successfully synced.
349
+ if (projectWithVersion.length) {
350
+ const versionMap: Record<string, string> = {};
351
+ projectWithVersion.forEach(
352
+ (p) => (versionMap[p.projectId] = p.versionRange)
353
+ );
354
+ context.config.projects.forEach(
355
+ (p) => (p.version = versionMap[p.projectId] || p.version)
356
+ );
357
+ }
358
+
359
+ // Fix imports
360
+ const fixImportContext = mkFixImportContext(context.config);
361
+ for (const m of pendingMerge) {
362
+ const resolvedEditedFile = replaceImports(
363
+ context,
364
+ m.editedSkeletonFile,
365
+ m.skeletonModulePath,
366
+ fixImportContext,
367
+ true,
368
+ baseDir
369
+ );
370
+ const resolvedNewFile = replaceImports(
371
+ context,
372
+ m.newSkeletonFile,
373
+ m.skeletonModulePath,
374
+ fixImportContext,
375
+ true,
376
+ baseDir
377
+ );
378
+ await m.merge(resolvedNewFile, resolvedEditedFile);
379
+ }
380
+ // Now we know config.components are all correct, so we can go ahead and fix up all the import statements
381
+ await fixAllImportStatements(context, opts.baseDir, summary);
382
+
383
+ if (process.env.PLASMIC_LOADER) {
384
+ const rootProjectIds = new Set(projectSyncParams.map((p) => p.projectId));
385
+ const freshIdsAndTokens = projectIdsAndTokens
386
+ .filter((p) => rootProjectIds.has(p.projectId))
387
+ .map((p) => L.pick(p, "projectId", "projectApiToken"));
388
+
389
+ const config: Partial<PlasmicLoaderConfig> = {
390
+ ...loaderConfig,
391
+ projects: L.sortBy(
392
+ L.uniqBy(
393
+ [...freshIdsAndTokens, ...(loaderConfig?.projects ?? [])],
394
+ (p) => p.projectId
395
+ ),
396
+ (p) => p.projectId
397
+ ),
398
+ };
399
+
400
+ writeLoaderConfig(opts, config);
401
+ }
402
+
403
+ // Write the new ComponentConfigs to disk
404
+ await updateConfig(context, context.config, baseDir);
405
+ });
406
+
407
+ // Post-sync commands
408
+ if (!opts.ignorePostSync) {
409
+ for (const cmd of context.config.postSyncCommands || []) {
410
+ spawnSync(cmd, { shell: true, stdio: "inherit" });
411
+ }
412
+ }
413
+
414
+ if (isFirstRun) {
415
+ if (!process.env.QUIET) {
416
+ printFirstSyncInfo(context);
417
+ }
418
+ }
419
+ }
420
+
421
+ function maybeRenamePathExt(
422
+ context: PlasmicContext,
423
+ path: string,
424
+ ext: string
425
+ ) {
426
+ if (!path) {
427
+ return path;
428
+ }
429
+ const correctPath = `${stripExtension(path, true)}${ext}`;
430
+ if (path !== correctPath) {
431
+ renameFile(context, path, correctPath);
432
+ }
433
+ return correctPath;
434
+ }
435
+
436
+ function fixFileExtension(context: PlasmicContext) {
437
+ const cssExt =
438
+ context.config.style.scheme === "css-modules" ? ".module.css" : ".css";
439
+ context.config.style.defaultStyleCssFilePath = maybeRenamePathExt(
440
+ context,
441
+ context.config.style.defaultStyleCssFilePath,
442
+ cssExt
443
+ );
444
+ context.config.projects.forEach((project) => {
445
+ project.cssFilePath = maybeRenamePathExt(
446
+ context,
447
+ project.cssFilePath,
448
+ cssExt
449
+ );
450
+ project.components.forEach((component) => {
451
+ component.cssFilePath = maybeRenamePathExt(
452
+ context,
453
+ component.cssFilePath,
454
+ cssExt
455
+ );
456
+ });
457
+ });
458
+ }
459
+
460
+ async function syncProject(
461
+ context: PlasmicContext,
462
+ opts: SyncArgs,
463
+ projectIdsAndTokens: ProjectIdAndToken[],
464
+ projectId: string,
465
+ componentIds: string[],
466
+ projectVersion: string,
467
+ dependencies: { [projectId: string]: string },
468
+ summary: Map<string, ComponentUpdateSummary>,
469
+ pendingMerge: ComponentPendingMerge[],
470
+ metadataDefaults?: Metadata
471
+ ): Promise<void> {
472
+ const newComponentScheme =
473
+ opts.newComponentScheme || context.config.code.scheme;
474
+ const existingProject = context.config.projects.find(
475
+ (p) => p.projectId === projectId
476
+ );
477
+ const existingCompScheme: Array<[string, "blackbox" | "direct"]> = (
478
+ existingProject?.components || []
479
+ ).map((c) => [c.id, c.scheme]);
480
+
481
+ const projectApiToken = ensure(
482
+ projectIdsAndTokens.find((p) => p.projectId === projectId)?.projectApiToken,
483
+ `Could not find the API token for project ${projectId} in list: ${JSON.stringify(
484
+ projectIdsAndTokens
485
+ )}`
486
+ );
487
+
488
+ const existingChecksums = getChecksums(
489
+ context,
490
+ opts,
491
+ projectId,
492
+ componentIds
493
+ );
494
+
495
+ // Server-side code-gen
496
+ const projectBundle = await context.api.projectComponents(projectId, {
497
+ platform: context.config.platform,
498
+ newCompScheme: newComponentScheme,
499
+ existingCompScheme,
500
+ componentIdOrNames: componentIds,
501
+ version: projectVersion,
502
+ imageOpts: context.config.images,
503
+ stylesOpts: context.config.style,
504
+ checksums: existingChecksums,
505
+ codeOpts: context.config.code,
506
+ metadata: generateMetadata(
507
+ {
508
+ ...metadataDefaults,
509
+ platform: context.config.platform,
510
+ },
511
+ opts.metadata
512
+ ),
513
+ });
514
+
515
+ // Convert from TSX => JSX
516
+ if (context.config.code.lang === "js") {
517
+ projectBundle.components.forEach((c) => {
518
+ [c.renderModuleFileName, c.renderModule] = maybeConvertTsxToJsx(
519
+ c.renderModuleFileName,
520
+ c.renderModule,
521
+ opts.baseDir
522
+ );
523
+ [c.skeletonModuleFileName, c.skeletonModule] = maybeConvertTsxToJsx(
524
+ c.skeletonModuleFileName,
525
+ c.skeletonModule,
526
+ opts.baseDir
527
+ );
528
+ });
529
+ projectBundle.iconAssets.forEach((icon) => {
530
+ [icon.fileName, icon.module] = maybeConvertTsxToJsx(
531
+ icon.fileName,
532
+ icon.module,
533
+ opts.baseDir
534
+ );
535
+ });
536
+ projectBundle.globalVariants.forEach((gv) => {
537
+ [gv.contextFileName, gv.contextModule] = maybeConvertTsxToJsx(
538
+ gv.contextFileName,
539
+ gv.contextModule,
540
+ opts.baseDir
541
+ );
542
+ });
543
+ (projectBundle.projectConfig.jsBundleThemes || []).forEach((theme) => {
544
+ [theme.themeFileName, theme.themeModule] = maybeConvertTsxToJsx(
545
+ theme.themeFileName,
546
+ theme.themeModule,
547
+ opts.baseDir
548
+ );
549
+ });
550
+ }
551
+ await syncGlobalVariants(
552
+ context,
553
+ projectBundle.projectConfig,
554
+ projectBundle.globalVariants,
555
+ projectBundle.checksums,
556
+ opts.baseDir
557
+ );
558
+
559
+ syncCodeComponentsMeta(context, projectId, projectBundle.codeComponentMetas);
560
+
561
+ await syncProjectConfig(
562
+ context,
563
+ projectBundle.projectConfig,
564
+ projectApiToken,
565
+ projectVersion,
566
+ dependencies,
567
+ projectBundle.components,
568
+ opts.forceOverwrite,
569
+ !!opts.appendJsxOnMissingBase,
570
+ summary,
571
+ pendingMerge,
572
+ projectBundle.checksums,
573
+ opts.baseDir
574
+ );
575
+ await upsertStyleTokens(context, projectBundle.usedTokens);
576
+ await syncProjectIconAssets(
577
+ context,
578
+ projectId,
579
+ projectVersion,
580
+ projectBundle.iconAssets,
581
+ projectBundle.checksums,
582
+ opts.baseDir
583
+ );
584
+ await syncProjectImageAssets(
585
+ context,
586
+ projectId,
587
+ projectVersion,
588
+ projectBundle.imageAssets,
589
+ projectBundle.checksums
590
+ );
591
+ }
592
+
593
+ async function syncStyleConfig(
594
+ context: PlasmicContext,
595
+ response: StyleConfigResponse
596
+ ) {
597
+ const expectedPath =
598
+ context.config.style.defaultStyleCssFilePath ||
599
+ path.join(
600
+ context.config.defaultPlasmicDir,
601
+ response.defaultStyleCssFileName
602
+ );
603
+ context.config.style.defaultStyleCssFilePath = expectedPath;
604
+ await writeFileContent(context, expectedPath, response.defaultStyleCssRules, {
605
+ force: true,
606
+ });
607
+ }
608
+
609
+ async function syncProjectConfig(
610
+ context: PlasmicContext,
611
+ projectBundle: ProjectMetaBundle,
612
+ projectApiToken: string,
613
+ version: string,
614
+ dependencies: { [projectId: string]: string },
615
+ componentBundles: ComponentBundle[],
616
+ forceOverwrite: boolean,
617
+ appendJsxOnMissingBase: boolean,
618
+ summary: Map<string, ComponentUpdateSummary>,
619
+ pendingMerge: ComponentPendingMerge[],
620
+ checksums: ChecksumBundle,
621
+ baseDir: string
622
+ ) {
623
+ const defaultCssFilePath = defaultResourcePath(
624
+ context,
625
+ projectBundle.projectName,
626
+ projectBundle.cssFileName
627
+ );
628
+ const isNew = !context.config.projects.find(
629
+ (p) => p.projectId === projectBundle.projectId
630
+ );
631
+
632
+ // If latest, use that as the range, otherwise set to latest published (>=0.0.0)
633
+ const versionRange = semver.isLatest(version) ? version : ">=0.0.0";
634
+ const projectConfig = getOrAddProjectConfig(
635
+ context,
636
+ projectBundle.projectId,
637
+ createProjectConfig({
638
+ projectId: projectBundle.projectId,
639
+ projectApiToken,
640
+ projectName: projectBundle.projectName,
641
+ version: versionRange,
642
+ cssFilePath: defaultCssFilePath,
643
+ })
644
+ );
645
+
646
+ // Update missing/outdated props
647
+ projectConfig.projectName = projectBundle.projectName;
648
+ if (!projectConfig.cssFilePath) {
649
+ projectConfig.cssFilePath = defaultCssFilePath;
650
+ }
651
+ projectConfig.projectApiToken = projectApiToken;
652
+
653
+ // plasmic.lock
654
+ const projectLock = getOrAddProjectLock(context, projectConfig.projectId);
655
+ projectLock.version = version;
656
+ projectLock.dependencies = dependencies;
657
+ projectLock.lang = context.config.code.lang;
658
+
659
+ if (projectBundle.cssRules) {
660
+ const formattedCssRules = formatAsLocal(
661
+ projectBundle.cssRules,
662
+ projectConfig.cssFilePath,
663
+ baseDir
664
+ );
665
+
666
+ // Write out project css
667
+ await writeFileContent(
668
+ context,
669
+ projectConfig.cssFilePath,
670
+ formattedCssRules,
671
+ {
672
+ force: !isNew,
673
+ }
674
+ );
675
+ }
676
+ projectLock.fileLocks = projectLock.fileLocks.filter(
677
+ (fileLock) => fileLock.type !== "projectCss"
678
+ );
679
+ projectLock.fileLocks.push({
680
+ assetId: projectConfig.projectId,
681
+ type: "projectCss",
682
+ checksum: checksums.projectCssChecksum,
683
+ });
684
+
685
+ /*
686
+ for (const theme of projectBundle.jsBundleThemes) {
687
+ if (!projectConfig.jsBundleThemes) {
688
+ projectConfig.jsBundleThemes = [];
689
+ }
690
+ let themeConfig = projectConfig.jsBundleThemes.find(
691
+ (c) => c.bundleName === theme.bundleName
692
+ );
693
+ if (!themeConfig) {
694
+ const themeFilePath = defaultResourcePath(
695
+ context,
696
+ projectConfig,
697
+ theme.themeFileName
698
+ );
699
+ themeConfig = { themeFilePath, bundleName: theme.bundleName };
700
+ projectConfig.jsBundleThemes.push(themeConfig);
701
+ }
702
+ const formatted = formatAsLocal(
703
+ theme.themeModule,
704
+ themeConfig.themeFilePath
705
+ );
706
+ await writeFileContent(context, themeConfig.themeFilePath, formatted, {
707
+ force: true,
708
+ });
709
+ }
710
+ */
711
+
712
+ if (
713
+ projectConfig.jsBundleThemes &&
714
+ projectConfig.jsBundleThemes.length === 0
715
+ ) {
716
+ delete projectConfig.jsBundleThemes;
717
+ }
718
+
719
+ // Write out components
720
+ await syncProjectComponents(
721
+ context,
722
+ projectConfig,
723
+ version,
724
+ componentBundles,
725
+ forceOverwrite,
726
+ appendJsxOnMissingBase,
727
+ summary,
728
+ pendingMerge,
729
+ projectLock,
730
+ checksums,
731
+ baseDir
732
+ );
733
+ }
734
+
735
+ function syncCodeComponentsMeta(
736
+ context: PlasmicContext,
737
+ projectId: string,
738
+ codeComponentBundles: CodeComponentMeta[]
739
+ ) {
740
+ const projectConfig = getOrAddProjectConfig(context, projectId);
741
+
742
+ projectConfig.codeComponents = codeComponentBundles.map((meta) => ({
743
+ id: meta.id,
744
+ name: meta.name,
745
+ componentImportPath: meta.importPath,
746
+ }));
747
+ }