@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,656 @@
1
+ import * as babel from "@babel/core";
2
+ import generate from "@babel/generator";
3
+ import * as parser from "@babel/parser";
4
+ import traverse, { Node } from "@babel/traverse";
5
+ import { ImportDeclaration } from "@babel/types";
6
+ import L from "lodash";
7
+ import * as Prettier from "prettier";
8
+ import { Options, resolveConfig } from "prettier";
9
+ import * as ts from "typescript";
10
+ import path from "upath";
11
+ import {
12
+ fixComponentCssReferences,
13
+ fixComponentImagesReferences,
14
+ } from "../actions/sync-images";
15
+ import { logger } from "../deps";
16
+ import { HandledError } from "../utils/error";
17
+ import {
18
+ CodeComponentConfig,
19
+ ComponentConfig,
20
+ GlobalVariantGroupConfig,
21
+ IconConfig,
22
+ ImageConfig,
23
+ PlasmicConfig,
24
+ PlasmicContext,
25
+ ProjectConfig,
26
+ } from "./config-utils";
27
+ import {
28
+ existsBuffered,
29
+ makeFilePath,
30
+ readFileText,
31
+ stripExtension,
32
+ writeFileContent,
33
+ } from "./file-utils";
34
+ import { assert, flatMap } from "./lang-utils";
35
+
36
+ export const formatAsLocal = (
37
+ content: string,
38
+ filePath: string,
39
+ baseDir: string,
40
+ defaultOpts: Options = {}
41
+ ) => {
42
+ const opts = resolveConfig.sync(baseDir) || defaultOpts;
43
+ opts.filepath = filePath;
44
+
45
+ // Running Prettier multiple times may actually yield different results!
46
+ // Here we run it twice, just to be safe... :-/
47
+ const res = Prettier.format(content, opts);
48
+ const res2 = Prettier.format(res, opts);
49
+ return res2;
50
+ };
51
+
52
+ const nodeToFormattedCode = (
53
+ n: Node,
54
+ baseDir: string,
55
+ unformatted?: boolean,
56
+ commentsToRemove?: Set<string>
57
+ ) => {
58
+ const c = generate(n, {
59
+ retainLines: true,
60
+ shouldPrintComment: (c) => !commentsToRemove || !commentsToRemove.has(c),
61
+ }).code;
62
+ return unformatted
63
+ ? c
64
+ : formatAsLocal(c, "/tmp/x.tsx", baseDir, {
65
+ trailingComma: "none",
66
+ arrowParens: "avoid",
67
+ });
68
+ };
69
+
70
+ function findImportSpecifierWithAlias(
71
+ importDecl: ImportDeclaration,
72
+ local: string
73
+ ) {
74
+ for (const spec of importDecl.specifiers) {
75
+ if (spec.type === "ImportSpecifier" && spec.local.name === local) {
76
+ return spec;
77
+ }
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ function findImportDefaultSpecifier(importDecl: ImportDeclaration) {
83
+ for (const spec of importDecl.specifiers) {
84
+ if (spec.type === "ImportDefaultSpecifier") {
85
+ return spec;
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ export function ensureImportSpecifierWithAlias(
92
+ decl: ImportDeclaration,
93
+ imported: string,
94
+ alias: string
95
+ ) {
96
+ const existing = findImportSpecifierWithAlias(decl, alias);
97
+ if (existing) {
98
+ if (existing.imported.type === "Identifier") {
99
+ existing.imported.name = imported;
100
+ } else {
101
+ existing.imported.value = imported;
102
+ }
103
+ } else {
104
+ decl.specifiers = decl.specifiers.filter((specifier) => {
105
+ if (
106
+ specifier.type === "ImportDefaultSpecifier" &&
107
+ specifier.local.name === alias
108
+ ) {
109
+ // If we are importing a default for a name that will collide with our
110
+ // desired alias, then the default import is wrong and we skip it.
111
+ return false;
112
+ }
113
+ return true;
114
+ });
115
+ decl.specifiers.push(
116
+ babel.types.importSpecifier(
117
+ babel.types.identifier(alias),
118
+ babel.types.identifier(imported)
119
+ )
120
+ );
121
+ }
122
+ }
123
+
124
+ export function ensureImportDefaultSpecifier(
125
+ decl: ImportDeclaration,
126
+ defaultExport: string
127
+ ) {
128
+ const existing = findImportDefaultSpecifier(decl);
129
+ if (existing) {
130
+ existing.local.name = defaultExport;
131
+ } else {
132
+ decl.specifiers.splice(
133
+ 0,
134
+ 0,
135
+ babel.types.importDefaultSpecifier(babel.types.identifier(defaultExport))
136
+ );
137
+ }
138
+ }
139
+
140
+ interface PlasmicImportSpec {
141
+ id: string;
142
+ type: PlasmicImportType;
143
+ node: ImportDeclaration;
144
+ }
145
+
146
+ type PlasmicImportType =
147
+ | "render"
148
+ | "css"
149
+ | "component"
150
+ | "globalVariant"
151
+ | "projectcss"
152
+ | "defaultcss"
153
+ | "icon"
154
+ | "picture"
155
+ | "jsBundle"
156
+ | "codeComponent"
157
+ | undefined;
158
+
159
+ function tryParsePlasmicImportSpec(node: ImportDeclaration) {
160
+ const c = node.trailingComments?.[0];
161
+ if (!c) {
162
+ return undefined;
163
+ }
164
+ const m = c.value.match(
165
+ /plasmic-import:\s+([\w-]+)(?:\/(component|css|render|globalVariant|projectcss|defaultcss|icon|picture|jsBundle|codeComponent))?/
166
+ );
167
+ if (m) {
168
+ return { id: m[1], type: m[2] as PlasmicImportType } as PlasmicImportSpec;
169
+ }
170
+ return undefined;
171
+ }
172
+
173
+ function filterUnformattedMarker(code: string, changed: boolean) {
174
+ const lines = code.split("\n");
175
+ const isUnformattedMarker = (line: string) =>
176
+ line.trim() === "// plasmic-unformatted";
177
+ changed = changed || lines.some(isUnformattedMarker);
178
+ code = lines.filter((line) => !isUnformattedMarker(line)).join("\n");
179
+ return [code, changed] as const;
180
+ }
181
+
182
+ /**
183
+ * Given the argument `code` string, for module at `fromPath`, replaces all Plasmic imports
184
+ * for modules found in `compConfigsMap`.
185
+ */
186
+ export function replaceImports(
187
+ context: PlasmicContext,
188
+ code: string,
189
+ fromPath: string,
190
+ fixImportContext: FixImportContext,
191
+ removeImportDirective: boolean,
192
+ baseDir: string,
193
+ changed = false
194
+ ) {
195
+ [code, changed] = filterUnformattedMarker(code, changed);
196
+ const file = parser.parse(code, {
197
+ strictMode: true,
198
+ sourceType: "module",
199
+ plugins: [
200
+ // At a minimum, we need to parse jsx and typescript
201
+ "jsx",
202
+ "typescript",
203
+
204
+ // There are also various features that people may have... May just
205
+ // need to add as we encounter them...
206
+ "classProperties",
207
+ ],
208
+ });
209
+ const commentsToRemove = new Set<string>();
210
+ file.program.body.forEach((stmt) => {
211
+ if (stmt.type !== "ImportDeclaration") {
212
+ return;
213
+ }
214
+ const importStmt: ImportDeclaration = stmt;
215
+ const spec = tryParsePlasmicImportSpec(importStmt);
216
+ if (!spec) {
217
+ return;
218
+ }
219
+ changed = true;
220
+ if (removeImportDirective) {
221
+ commentsToRemove.add(stmt.trailingComments?.[0].value || "");
222
+ }
223
+ const type = spec.type;
224
+ const uuid = spec.id;
225
+ if (type === "component") {
226
+ // instantiation of a mapped or managed component
227
+ const compConfig = fixImportContext.components[uuid];
228
+ if (!compConfig) {
229
+ throwMissingReference(context, "component", uuid, fromPath);
230
+ }
231
+ const { modulePath, exportName } = compConfig.importSpec;
232
+ if (exportName) {
233
+ // ensure import { ${exportName} as ${compConfig.name} }
234
+ ensureImportSpecifierWithAlias(stmt, exportName, compConfig.name);
235
+ } else {
236
+ // Keep the same name as it might be different from compConfig.name due
237
+ // to name collisions.
238
+ // ensureImportDefaultSpecifier(stmt, compConfig.name);
239
+ }
240
+ const realPath = makeImportPath(context, fromPath, modulePath, true);
241
+ stmt.source.value = realPath;
242
+ } else if (type === "render") {
243
+ // import of the PP blackbox
244
+ const compConfig = fixImportContext.components[uuid];
245
+ if (!compConfig) {
246
+ throwMissingReference(context, "component", uuid, fromPath);
247
+ }
248
+ const realPath = makeImportPath(
249
+ context,
250
+ fromPath,
251
+ compConfig.renderModuleFilePath,
252
+ true
253
+ );
254
+ stmt.source.value = realPath;
255
+ } else if (type === "css") {
256
+ // import of the PP css file
257
+ const compConfig = fixImportContext.components[uuid];
258
+ if (!compConfig) {
259
+ throwMissingReference(context, "component", uuid, fromPath);
260
+ }
261
+ const realPath = makeImportPath(
262
+ context,
263
+ fromPath,
264
+ compConfig.cssFilePath,
265
+ false
266
+ );
267
+ stmt.source.value = realPath;
268
+ } else if (type === "globalVariant") {
269
+ // import of global context
270
+ const variantConfig = fixImportContext.globalVariants[uuid];
271
+ if (!variantConfig) {
272
+ throwMissingReference(context, "global variant", uuid, fromPath);
273
+ }
274
+ const realPath = makeImportPath(
275
+ context,
276
+ fromPath,
277
+ variantConfig.contextFilePath,
278
+ true
279
+ );
280
+ stmt.source.value = realPath;
281
+ } else if (type === "icon") {
282
+ // import of global context
283
+ const iconConfig = fixImportContext.icons[uuid];
284
+ if (!iconConfig) {
285
+ throwMissingReference(context, "icon", uuid, fromPath);
286
+ }
287
+ const realPath = makeImportPath(
288
+ context,
289
+ fromPath,
290
+ iconConfig.moduleFilePath,
291
+ true
292
+ );
293
+ stmt.source.value = realPath;
294
+ } else if (type === "picture") {
295
+ const imageConfig = fixImportContext.images[uuid];
296
+ if (!imageConfig) {
297
+ throwMissingReference(context, "image", uuid, fromPath);
298
+ }
299
+ const realPath = makeImportPath(
300
+ context,
301
+ fromPath,
302
+ imageConfig.filePath,
303
+ false
304
+ );
305
+ stmt.source.value = realPath;
306
+ } else if (type === "projectcss") {
307
+ const projectConfig = fixImportContext.projects[uuid];
308
+ if (!projectConfig) {
309
+ throwMissingReference(context, "project", uuid, fromPath);
310
+ }
311
+ const realPath = makeImportPath(
312
+ context,
313
+ fromPath,
314
+ projectConfig.cssFilePath,
315
+ false
316
+ );
317
+ stmt.source.value = realPath;
318
+ } else if (type === "defaultcss") {
319
+ const realPath = makeImportPath(
320
+ context,
321
+ fromPath,
322
+ fixImportContext.config.style.defaultStyleCssFilePath,
323
+ false
324
+ );
325
+ stmt.source.value = realPath;
326
+ } else if (type === "codeComponent") {
327
+ const meta = fixImportContext.codeComponentMetas[uuid];
328
+ if (meta.componentImportPath[0] === ".") {
329
+ // Relative path from the project root
330
+ const toPath = path.join(process.cwd(), meta.componentImportPath);
331
+ assert(path.isAbsolute(toPath));
332
+ const realPath = makeImportPath(context, fromPath, toPath, true, true);
333
+ stmt.source.value = realPath;
334
+ } else {
335
+ // npm package
336
+ stmt.source.value = meta.componentImportPath;
337
+ }
338
+ }
339
+ });
340
+
341
+ if (!changed) {
342
+ return code;
343
+ }
344
+
345
+ return nodeToFormattedCode(file, baseDir, !changed, commentsToRemove);
346
+ }
347
+
348
+ function throwMissingReference(
349
+ context: PlasmicContext,
350
+ itemType: string,
351
+ uuid: string,
352
+ fromPath: string
353
+ ) {
354
+ let recFix = `Please make sure projects that "${uuid}" depends on have already been synced.`;
355
+ if (context.cliArgs.nonRecursive) {
356
+ recFix = `Please run "plasmic sync" without the --non-recursive flag to sync dependencies.`;
357
+ } else if (!context.cliArgs.force) {
358
+ recFix = `Please run "plasmic sync" with --force flag to force-sync the dependencies.`;
359
+ }
360
+ throw new HandledError(
361
+ `Encountered Plasmic ${itemType} "${uuid}" in ${fromPath} that are being used but have not been synced. ${recFix}`
362
+ );
363
+ }
364
+
365
+ function makeImportPath(
366
+ context: PlasmicContext,
367
+ fromPath: string,
368
+ toPath: string,
369
+ stripExt: boolean,
370
+ forceRelative = false
371
+ ) {
372
+ let result = toPath;
373
+ if (forceRelative || isLocalModulePath(toPath)) {
374
+ result = path.relative(
375
+ makeFilePath(context, path.dirname(fromPath)),
376
+ makeFilePath(context, toPath)
377
+ );
378
+ if (!result.startsWith(".")) {
379
+ result = `./${result}`;
380
+ }
381
+ }
382
+ if (stripExt) {
383
+ result = stripExtension(result);
384
+ }
385
+ return result;
386
+ }
387
+
388
+ /**
389
+ * Returns true if the argument ComponentConfig.importSpec.modulePath is referencing
390
+ * a local file
391
+ */
392
+ export function isLocalModulePath(modulePath: string) {
393
+ // It is a local file reference if the importPath includes the file extension
394
+ return !!path.extname(modulePath);
395
+ }
396
+
397
+ export interface ComponentUpdateSummary {
398
+ // Whether the skeleton module was modified or created.
399
+ skeletonModuleModified: boolean;
400
+ }
401
+
402
+ export interface FixImportContext {
403
+ config: PlasmicConfig;
404
+ components: Record<string, ComponentConfig>;
405
+ codeComponentMetas: Record<string, CodeComponentConfig>;
406
+ globalVariants: Record<string, GlobalVariantGroupConfig>;
407
+ icons: Record<string, IconConfig>;
408
+ images: Record<string, ImageConfig>;
409
+ projects: Record<string, ProjectConfig>;
410
+ }
411
+
412
+ export const mkFixImportContext = (config: PlasmicConfig) => {
413
+ const allComponents = flatMap(config.projects, (p) => p.components);
414
+ const components = L.keyBy(allComponents, (c) => c.id);
415
+ const allCodeComponents = flatMap(
416
+ config.projects,
417
+ (p) => p.codeComponents || []
418
+ );
419
+ const codeComponentMetas = L.keyBy(allCodeComponents, (c) => c.id);
420
+ const globalVariants = L.keyBy(
421
+ config.globalVariants.variantGroups,
422
+ (c) => c.id
423
+ );
424
+ const icons = L.keyBy(
425
+ flatMap(config.projects, (p) => p.icons),
426
+ (c) => c.id
427
+ );
428
+ const images = L.keyBy(
429
+ flatMap(config.projects, (p) => p.images),
430
+ (c) => c.id
431
+ );
432
+ const projects = L.keyBy(config.projects, (p) => p.projectId);
433
+ return {
434
+ config,
435
+ components,
436
+ codeComponentMetas,
437
+ globalVariants,
438
+ icons,
439
+ images,
440
+ projects,
441
+ };
442
+ };
443
+
444
+ /**
445
+ * Assuming that all the files referenced in PlasmicConfig are correct, fixes import statements using PlasmicConfig
446
+ * file locations as the source of truth.
447
+ */
448
+ export async function fixAllImportStatements(
449
+ context: PlasmicContext,
450
+ baseDir: string,
451
+ summary?: Map<string, ComponentUpdateSummary>
452
+ ) {
453
+ logger.info("Fixing import statements...");
454
+ const config = context.config;
455
+ const fixImportContext = mkFixImportContext(config);
456
+ for (const project of config.projects) {
457
+ for (const compConfig of project.components) {
458
+ const compSummary = summary?.get(compConfig.id);
459
+ if (summary && !compSummary) {
460
+ continue;
461
+ }
462
+ const fixSkeletonModule = compSummary
463
+ ? compSummary.skeletonModuleModified
464
+ : true;
465
+ if (!summary || compSummary) {
466
+ await fixComponentImportStatements(
467
+ context,
468
+ compConfig,
469
+ fixImportContext,
470
+ fixSkeletonModule,
471
+ baseDir
472
+ );
473
+ }
474
+ }
475
+ }
476
+ }
477
+
478
+ async function fixComponentImportStatements(
479
+ context: PlasmicContext,
480
+ compConfig: ComponentConfig,
481
+ fixImportContext: FixImportContext,
482
+ fixSkeletonModule: boolean,
483
+ baseDir: string
484
+ ) {
485
+ // If ComponentConfig.importPath is still a local file, we best-effort also fix up the import statements there.
486
+ if (
487
+ isLocalModulePath(compConfig.importSpec.modulePath) &&
488
+ fixSkeletonModule
489
+ ) {
490
+ await fixFileImportStatements(
491
+ context,
492
+ compConfig.importSpec.modulePath,
493
+ fixImportContext,
494
+ true,
495
+ baseDir
496
+ );
497
+ }
498
+
499
+ let renderModuleChanged = false;
500
+
501
+ if (["files", "public-files"].includes(context.config.images.scheme)) {
502
+ await fixComponentCssReferences(
503
+ context,
504
+ fixImportContext,
505
+ compConfig.cssFilePath
506
+ );
507
+ if (context.config.images.scheme === "public-files") {
508
+ renderModuleChanged = await fixComponentImagesReferences(
509
+ context,
510
+ fixImportContext,
511
+ compConfig.renderModuleFilePath
512
+ );
513
+ }
514
+ }
515
+
516
+ // Fix file imports and run prettier just after fixing image references
517
+ await fixFileImportStatements(
518
+ context,
519
+ compConfig.renderModuleFilePath,
520
+ fixImportContext,
521
+ false,
522
+ baseDir,
523
+ renderModuleChanged
524
+ );
525
+ }
526
+
527
+ async function fixFileImportStatements(
528
+ context: PlasmicContext,
529
+ srcDirFilePath: string,
530
+ fixImportContext: FixImportContext,
531
+ removeImportDirective: boolean,
532
+ baseDir: string,
533
+ fileHasChanged = false
534
+ ) {
535
+ const filePath = makeFilePath(context, srcDirFilePath);
536
+ if (!existsBuffered(filePath)) {
537
+ logger.warn(
538
+ `Cannot fix imports in non-existing file ${srcDirFilePath}. Check your plasmic.json file for invalid entries.`
539
+ );
540
+ return;
541
+ }
542
+
543
+ const prevContent = readFileText(filePath).toString();
544
+
545
+ const newContent = replaceImports(
546
+ context,
547
+ prevContent,
548
+ srcDirFilePath,
549
+ fixImportContext,
550
+ removeImportDirective,
551
+ baseDir,
552
+ fileHasChanged
553
+ );
554
+ if (prevContent !== newContent) {
555
+ await writeFileContent(context, srcDirFilePath, newContent, {
556
+ force: true,
557
+ });
558
+ }
559
+ }
560
+
561
+ class CompilerOptions {
562
+ private static opts: ts.CompilerOptions | undefined = undefined;
563
+
564
+ static getOpts() {
565
+ if (!this.opts) {
566
+ let curDir = __dirname;
567
+ let configPath = "";
568
+ do {
569
+ curDir = path.join(curDir, "..");
570
+ configPath = path.join(curDir, "tsconfig-transform.json");
571
+ } while (!existsBuffered(configPath));
572
+ const c = ts.readConfigFile(configPath, (path) => readFileText(path));
573
+ this.opts = ts.convertCompilerOptionsFromJson(
574
+ c.config.compilerOptions,
575
+ "."
576
+ ).options;
577
+ }
578
+ return this.opts;
579
+ }
580
+ }
581
+
582
+ export const tsxToJsx = (code: string) => {
583
+ // when the code has jsx pragma, typescript will remove comments, and remove
584
+ // "import React from 'React'" if React is unused. So we first invalidate it.
585
+ // We also need to add the usageMagic to prevent typescript from remove the
586
+ // import of ncreatePlasmicElementProxy.
587
+ const usageMagic = "\ncreatePlasmicElementProxy();";
588
+ const jsxPragmas = ["jsx", "jsxFrag", "jsxRuntime", "jsxImportSource"];
589
+
590
+ function prepForTranspile(str: string) {
591
+ for (const p of jsxPragmas) {
592
+ str = str.replace(`/** @${p} `, `/** @ ${p} `);
593
+ }
594
+ return str + usageMagic;
595
+ }
596
+
597
+ function fixPostTranspile(str: string) {
598
+ for (const p of jsxPragmas) {
599
+ str = str.replace(`/** @ ${p} `, `/** @${p} `);
600
+ }
601
+ str = str.replace(usageMagic, "");
602
+ return str;
603
+ }
604
+
605
+ let result = ts.transpileModule(prepForTranspile(code), {
606
+ compilerOptions: CompilerOptions.getOpts(),
607
+ });
608
+ return fixPostTranspile(result.outputText);
609
+ };
610
+
611
+ export function maybeConvertTsxToJsx(
612
+ fileName: string,
613
+ content: string,
614
+ baseDir: string
615
+ ) {
616
+ if (fileName.endsWith("tsx")) {
617
+ const jsFileName = stripExtension(fileName) + ".jsx";
618
+ const jsContent = formatScript(tsxToJsx(content), baseDir);
619
+ return [jsFileName, jsContent];
620
+ }
621
+ return [fileName, content];
622
+ }
623
+
624
+ export const formatScript = (code: string, baseDir: string) => {
625
+ const file = parser.parse(code, {
626
+ strictMode: true,
627
+ sourceType: "module",
628
+ plugins: ["jsx", "typescript"],
629
+ });
630
+ let newLineMarker = "THIS_SHALL_BE_NEW_LINE";
631
+ while (code.includes(newLineMarker)) {
632
+ newLineMarker = newLineMarker + "_REALLY";
633
+ }
634
+ traverse(file, {
635
+ Statement: function (path) {
636
+ if (
637
+ file.program.body.includes(path.node) &&
638
+ path.node.type !== "ImportDeclaration"
639
+ ) {
640
+ path.insertBefore(babel.types.stringLiteral(newLineMarker));
641
+ path.skip();
642
+ }
643
+ },
644
+ });
645
+
646
+ const withmarkers = nodeToFormattedCode(file, baseDir, true);
647
+ const withNewLines = withmarkers.replace(
648
+ new RegExp(`"${newLineMarker}"`, "g"),
649
+ "\n"
650
+ );
651
+ return formatAsLocal(withNewLines, "/tmp/x.tsx", baseDir, {
652
+ printWidth: 80,
653
+ tabWidth: 2,
654
+ useTabs: false,
655
+ });
656
+ };