@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,451 @@
1
+ import * as Sentry from "@sentry/node";
2
+ import L from "lodash";
3
+ import * as querystring from "querystring";
4
+ import path from "upath";
5
+ import { initPlasmic } from "../actions/init";
6
+ import { PlasmicApi } from "../api";
7
+ import { logger } from "../deps";
8
+ import { CommonArgs } from "../index";
9
+ import { runNecessaryMigrations } from "../migrations/migrations";
10
+ import { HandledError } from "../utils/error";
11
+ import { getCurrentAuth } from "./auth-utils";
12
+ import {
13
+ DEFAULT_HOST,
14
+ findConfigFile,
15
+ LOCK_FILE_NAME,
16
+ PlasmicConfig,
17
+ PlasmicContext,
18
+ PlasmicLock,
19
+ readConfig,
20
+ } from "./config-utils";
21
+ import {
22
+ buildBaseNameToFiles,
23
+ existsBuffered,
24
+ fileExists,
25
+ readFileText,
26
+ } from "./file-utils";
27
+ import { ensure } from "./lang-utils";
28
+ import { getCliVersion } from "./npm-utils";
29
+ import * as prompts from "./prompts";
30
+
31
+ function createPlasmicLock(): PlasmicLock {
32
+ return {
33
+ projects: [],
34
+ cliVersion: getCliVersion(),
35
+ };
36
+ }
37
+
38
+ export function readLock(lockFile: string): PlasmicLock {
39
+ if (!existsBuffered(lockFile)) {
40
+ return createPlasmicLock();
41
+ }
42
+ try {
43
+ const result = JSON.parse(readFileText(lockFile!)) as PlasmicLock;
44
+ return {
45
+ ...result,
46
+ };
47
+ } catch (e) {
48
+ logger.error(
49
+ `Error encountered reading ${LOCK_FILE_NAME} at ${lockFile}: ${e}`
50
+ );
51
+ throw e;
52
+ }
53
+ }
54
+
55
+ function removeMissingFilesFromLock(
56
+ context: PlasmicContext,
57
+ config: PlasmicConfig,
58
+ lock: PlasmicLock
59
+ ) {
60
+ const knownProjects = Object.fromEntries(
61
+ config.projects.map((project) => [project.projectId, project])
62
+ );
63
+ const knownGlobalVariants = Object.fromEntries(
64
+ context.config.globalVariants.variantGroups.map((vg) => [vg.projectId, vg])
65
+ );
66
+ lock.projects = lock.projects
67
+ .filter((project) => knownProjects[project.projectId])
68
+ .map((project) => {
69
+ const knownComponents = Object.fromEntries(
70
+ knownProjects[project.projectId].components.map((component) => [
71
+ component.id,
72
+ component,
73
+ ])
74
+ );
75
+ const knownImages = Object.fromEntries(
76
+ knownProjects[project.projectId].images.map((image) => [
77
+ image.id,
78
+ image,
79
+ ])
80
+ );
81
+ const knownIcons = Object.fromEntries(
82
+ knownProjects[project.projectId].images.map((icons) => [
83
+ icons.id,
84
+ icons,
85
+ ])
86
+ );
87
+
88
+ project.fileLocks = project.fileLocks.filter((lock) => {
89
+ switch (lock.type) {
90
+ default:
91
+ return false;
92
+ case "projectCss":
93
+ return knownProjects[project.projectId].cssFilePath;
94
+ case "globalVariant":
95
+ return knownGlobalVariants[project.projectId];
96
+ case "cssRules":
97
+ case "renderModule":
98
+ return knownComponents[lock.assetId];
99
+ case "image":
100
+ return knownImages[lock.assetId];
101
+ case "icon":
102
+ return knownIcons[lock.assetId];
103
+ }
104
+ });
105
+
106
+ return project;
107
+ });
108
+ }
109
+
110
+ async function attemptToRestoreFilePath(
111
+ context: PlasmicContext,
112
+ expectedPath: string,
113
+ baseNameToFiles: Record<string, string[]>
114
+ ) {
115
+ // If the path is not set, always recreate.
116
+ if (expectedPath === "") {
117
+ return undefined;
118
+ }
119
+
120
+ if (fileExists(context, expectedPath)) {
121
+ return expectedPath;
122
+ }
123
+ const fileName = path.basename(expectedPath);
124
+ if (!baseNameToFiles[fileName]) {
125
+ const answer = await prompts.askChoice({
126
+ message: `File ${path.join(
127
+ context.absoluteSrcDir,
128
+ expectedPath
129
+ )} not found. Do you want to recreate it?`,
130
+ choices: ["Yes", "No"],
131
+ defaultAnswer: "Yes",
132
+ hidePrompt: context.cliArgs.yes,
133
+ });
134
+
135
+ if (answer === "No") {
136
+ throw new HandledError(
137
+ "Please add this file or update your plasmic.json by removing or changing this path and try again."
138
+ );
139
+ }
140
+ return undefined;
141
+ }
142
+
143
+ if (baseNameToFiles[fileName].length === 1) {
144
+ const newPath = path.relative(
145
+ context.absoluteSrcDir,
146
+ baseNameToFiles[fileName][0]
147
+ );
148
+ logger.info(`\tDetected file moved from ${expectedPath} to ${newPath}.`);
149
+ return newPath;
150
+ }
151
+
152
+ // Multiple files
153
+ const none = "None.";
154
+ const answer = await prompts.askChoice({
155
+ message: `Cannot find expected file at ${expectedPath}. Please select one of the following matches:`,
156
+ choices: [...baseNameToFiles[fileName], none],
157
+ defaultAnswer: none,
158
+ hidePrompt: context.cliArgs.yes,
159
+ });
160
+
161
+ if (answer === none) {
162
+ throw new HandledError(
163
+ "Please add this file or update your plasmic.json by removing or changing this path and try again."
164
+ );
165
+ }
166
+ return answer;
167
+ }
168
+
169
+ async function resolveMissingFilesInConfig(
170
+ context: PlasmicContext,
171
+ config: PlasmicConfig
172
+ ) {
173
+ const baseNameToFiles = buildBaseNameToFiles(context);
174
+
175
+ // Make sure all the files exist. Prompt the user / exit process if not.
176
+ async function filterFiles<T>(list: T[], pathProp: keyof T) {
177
+ const newList = [];
178
+ for (const item of list) {
179
+ const newPath = await attemptToRestoreFilePath(
180
+ context,
181
+ item[pathProp] as any,
182
+ baseNameToFiles
183
+ );
184
+ if (newPath) {
185
+ item[pathProp] = newPath as any;
186
+ newList.push(item);
187
+ }
188
+ }
189
+ return newList;
190
+ }
191
+
192
+ context.config.globalVariants.variantGroups = await filterFiles(
193
+ context.config.globalVariants.variantGroups,
194
+ "contextFilePath"
195
+ );
196
+
197
+ context.config.style.defaultStyleCssFilePath =
198
+ (await attemptToRestoreFilePath(
199
+ context,
200
+ context.config.style.defaultStyleCssFilePath,
201
+ baseNameToFiles
202
+ )) || "";
203
+
204
+ for (const project of config.projects) {
205
+ project.cssFilePath =
206
+ (await attemptToRestoreFilePath(
207
+ context,
208
+ project.cssFilePath,
209
+ baseNameToFiles
210
+ )) || "";
211
+
212
+ project.images = await filterFiles(project.images, "filePath");
213
+ project.icons = await filterFiles(project.icons, "moduleFilePath");
214
+ project.jsBundleThemes = await filterFiles(
215
+ project.jsBundleThemes || [],
216
+ "themeFilePath"
217
+ );
218
+
219
+ // For components, if they decide to recreate in any of the path (css, wrapper, or blackbox component)
220
+ // we'll delete existing files.
221
+
222
+ const newComponents = [];
223
+ for (const component of project.components) {
224
+ if (component.type === "mapped") {
225
+ // For components mapped to an external module, we skip restoring files on disk
226
+ newComponents.push(component);
227
+ continue;
228
+ }
229
+ const newModulePath = await attemptToRestoreFilePath(
230
+ context,
231
+ component.importSpec.modulePath,
232
+ baseNameToFiles
233
+ );
234
+ if (!newModulePath) {
235
+ continue;
236
+ }
237
+ const newRenderModulePath = await attemptToRestoreFilePath(
238
+ context,
239
+ component.renderModuleFilePath,
240
+ baseNameToFiles
241
+ );
242
+
243
+ if (!newRenderModulePath) {
244
+ continue;
245
+ }
246
+
247
+ const newCssPath = await attemptToRestoreFilePath(
248
+ context,
249
+ component.cssFilePath,
250
+ baseNameToFiles
251
+ );
252
+
253
+ if (!newCssPath) {
254
+ continue;
255
+ }
256
+
257
+ component.importSpec.modulePath = newModulePath;
258
+ component.renderModuleFilePath = newRenderModulePath;
259
+ component.cssFilePath = newCssPath;
260
+ newComponents.push(component);
261
+ }
262
+ project.components = newComponents;
263
+ }
264
+ }
265
+
266
+ export async function getContext(
267
+ args: CommonArgs,
268
+ { enableSkipAuth = false }: { enableSkipAuth?: boolean } = {}
269
+ ): Promise<PlasmicContext> {
270
+ if (!args.baseDir) args.baseDir = process.cwd();
271
+ const auth = enableSkipAuth
272
+ ? await getCurrentOrDefaultAuth(args)
273
+ : await getOrInitAuth(args);
274
+
275
+ /** Sentry */
276
+ if (auth.host.startsWith(DEFAULT_HOST)) {
277
+ // Production usage of cli
278
+ Sentry.init({
279
+ dsn:
280
+ "https://3ed4eb43d28646e381bf3c50cff24bd6@o328029.ingest.sentry.io/5285892",
281
+ });
282
+ Sentry.configureScope((scope) => {
283
+ if (auth.user) {
284
+ scope.setUser({ email: auth.user });
285
+ }
286
+ scope.setExtra("cliVersion", getCliVersion());
287
+ scope.setExtra("args", JSON.stringify(args));
288
+ scope.setExtra("host", auth.host);
289
+ });
290
+ }
291
+
292
+ /** PlasmicConfig **/
293
+ let configFile =
294
+ args.config || findConfigFile(args.baseDir, { traverseParents: true });
295
+
296
+ if (!configFile) {
297
+ await maybeRunPlasmicInit(args, "plasmic.json", enableSkipAuth);
298
+ configFile = findConfigFile(args.baseDir, { traverseParents: true });
299
+ if (!configFile) {
300
+ const err = new HandledError(
301
+ "No plasmic.json file found. Please run `plasmic init` first."
302
+ );
303
+ throw err;
304
+ }
305
+ }
306
+ const rootDir = path.dirname(configFile);
307
+ // plasmic.lock should be in the same directory as plasmic.json
308
+ const lockFile = path.join(rootDir, LOCK_FILE_NAME);
309
+
310
+ await runNecessaryMigrations(configFile, lockFile, args.baseDir, args.yes);
311
+ const config = readConfig(configFile, true);
312
+
313
+ /** PlasmicLock */
314
+ const lock = readLock(lockFile);
315
+
316
+ const context = {
317
+ config,
318
+ configFile,
319
+ lock,
320
+ lockFile,
321
+ rootDir,
322
+ absoluteSrcDir: path.isAbsolute(config.srcDir)
323
+ ? config.srcDir
324
+ : path.resolve(rootDir, config.srcDir),
325
+ auth,
326
+ api: new PlasmicApi(auth),
327
+ cliArgs: args,
328
+ };
329
+
330
+ await resolveMissingFilesInConfig(context, config);
331
+ removeMissingFilesFromLock(context, config, lock);
332
+
333
+ return context;
334
+ }
335
+
336
+ /**
337
+ * Use empty user/token to signify no auth (only returning to provide a default host).
338
+ */
339
+ async function getCurrentOrDefaultAuth(args: CommonArgs) {
340
+ const auth = await getCurrentAuth(args.auth);
341
+ if (auth) {
342
+ return auth;
343
+ }
344
+
345
+ return {
346
+ host: DEFAULT_HOST,
347
+ user: "",
348
+ token: "",
349
+ };
350
+ }
351
+
352
+ async function getOrInitAuth(args: CommonArgs) {
353
+ const auth = await getCurrentAuth(args.auth);
354
+
355
+ if (auth) {
356
+ return auth;
357
+ }
358
+
359
+ if (await maybeRunPlasmicInit(args, ".plasmic.auth")) {
360
+ return ensure(await getCurrentAuth());
361
+ }
362
+
363
+ // Could not find the authentication credentials and the user
364
+ // declined to run plasmic init.
365
+ throw new HandledError("Could not authenticate.");
366
+ }
367
+
368
+ async function maybeRunPlasmicInit(
369
+ args: CommonArgs,
370
+ missingFile: string,
371
+ enableSkipAuth?: boolean
372
+ ): Promise<boolean> {
373
+ if (!process.env.QUIET) {
374
+ logger.info(`No ${missingFile} file found. Initializing plasmic...`);
375
+ }
376
+
377
+ await initPlasmic({
378
+ host: DEFAULT_HOST,
379
+ platform: "",
380
+ codeLang: "",
381
+ codeScheme: "",
382
+ styleScheme: "",
383
+ imagesScheme: "",
384
+ srcDir: "",
385
+ plasmicDir: "",
386
+ imagesPublicDir: "",
387
+ imagesPublicUrlPrefix: "",
388
+ enableSkipAuth,
389
+ ...args,
390
+ });
391
+ return true;
392
+ }
393
+
394
+ /**
395
+ * Table of where this metadata will be set
396
+ *
397
+ * Source | via |
398
+ * -------------------------------------------------------------------------
399
+ * cli | defaults | source=cli scheme=codegen command=sync|watch
400
+ * loader | defaults | source=loader scheme=loader command=sync|watch
401
+ * create-plasmic-app | env | source=create-plasmic-app
402
+ * plasmic-action | env | source=plasmic-action
403
+ */
404
+ export interface Metadata {
405
+ platform?: string; // from plasmic.json
406
+ source?: "cli" | "loader" | "create-plasmic-app" | "plasmic-action";
407
+ scheme?: "codegen" | "loader";
408
+ command?: "sync" | "watch";
409
+ }
410
+
411
+ /**
412
+ * Create a metadata bundle
413
+ * This will be used to tag Segment events (e.g. for codegen)
414
+ * Merges in:
415
+ * 1. defaults
416
+ * 2. PLASMIC_METADATA environment variable settings
417
+ * 3. arguments from the command-line
418
+ * to create a single Metadata object for Segment
419
+ * @param defaults
420
+ * @param fromArgs
421
+ */
422
+ export function generateMetadata(
423
+ defaults: Metadata,
424
+ fromArgs?: string
425
+ ): Metadata {
426
+ const fromEnv = process.env.PLASMIC_METADATA;
427
+ const metadataFromEnv = !fromEnv ? {} : { ...querystring.decode(fromEnv) };
428
+ const metadataFromArgs = !fromArgs ? {} : { ...querystring.decode(fromArgs) };
429
+ // Priority: 1. args 2. env 3. defaults
430
+ const metadata = L.assign({ ...defaults }, metadataFromEnv, metadataFromArgs);
431
+
432
+ return metadata;
433
+ }
434
+
435
+ /**
436
+ * This is meant to be called from consumers of the CLI to set
437
+ * metadata into the PLASMIC_METADATA environment variable
438
+ * @param metadata
439
+ */
440
+ export function setMetadataEnv(metadata: Metadata): void {
441
+ const fromEnv = process.env.PLASMIC_METADATA
442
+ ? querystring.decode(process.env.PLASMIC_METADATA)
443
+ : {};
444
+ const env = { ...fromEnv };
445
+ L.toPairs(metadata).forEach(([k, v]) => {
446
+ if (!env[k]) {
447
+ env[k] = v;
448
+ }
449
+ });
450
+ process.env.PLASMIC_METADATA = querystring.encode(env);
451
+ }
@@ -0,0 +1,75 @@
1
+ import chalk from "chalk";
2
+ import L from "lodash";
3
+ import wrapAnsi from "wrap-ansi";
4
+ import { logger } from "../deps";
5
+ import { PlasmicContext } from "./config-utils";
6
+ import { ensure } from "./lang-utils";
7
+
8
+ function wrap(str: string) {
9
+ return wrapAnsi(str, 60);
10
+ }
11
+
12
+ export function printFirstSyncInfo(context: PlasmicContext) {
13
+ const config = context.config;
14
+ if (config.code.scheme === "blackbox" && config.projects.length > 0) {
15
+ const project = ensure(L.last(config.projects));
16
+ logger.info(
17
+ `\nYour Plasmic project "${project.projectName}" has now been synced to disk.`
18
+ );
19
+ const exampleComponent = project.components[0];
20
+ if (exampleComponent) {
21
+ logger.info(
22
+ `
23
+
24
+ ${chalk.bold("Using Plasmic Components")}
25
+ ${chalk.bold("------------------------")}
26
+
27
+ ${wrap(
28
+ `For each component, Plasmic generates two React components in two files. For example, for component ${chalk.bold(
29
+ exampleComponent.name
30
+ )}, there are:`
31
+ )}
32
+
33
+ * A ${chalk.bold("blackbox")} component at ${chalk.bold.underline(
34
+ exampleComponent.renderModuleFilePath
35
+ )}
36
+ ${wrap(
37
+ `This is a blackbox, purely-presentational library component that you can use to render your designs. This file is owned by Plasmic, and you should not edit it -- it will be overwritten when the component design is updated. This component should only be used by the "wrapper" component (below).`
38
+ )}
39
+
40
+ * A ${chalk.bold("wrapper")} component at ${chalk.bold.underline(
41
+ exampleComponent.importSpec.modulePath
42
+ )}
43
+ ${wrap(
44
+ `This component is owned and edited by you to instantiate the Plasmic${exampleComponent.name} component with desired variants, states, event handlers, and data. You have complete control over this file, and this is the actual component that should be used by the rest of the codebase.`
45
+ )}
46
+
47
+ Learn more at https://www.plasmic.app/learn/codegen-guide/
48
+ `
49
+ );
50
+ }
51
+
52
+ const exampleIcon = project.icons[0];
53
+ if (exampleIcon) {
54
+ logger.info(
55
+ `
56
+ ${chalk.bold("Using Icons")}
57
+ ${chalk.bold("-----------")}
58
+
59
+ ${wrap(
60
+ `For each SVG icon, Plasmic also generates a React component. The component takes in all the usual props that you can pass to an svg element, and defaults to width/height of 1em.`
61
+ )}
62
+
63
+ For example, for the ${chalk.bold(
64
+ exampleIcon.name
65
+ )} icon at ${chalk.bold.underline(exampleIcon.moduleFilePath)},
66
+ instantiate it like:
67
+
68
+ ${chalk.green(`<${exampleIcon.name} color="red" />`)}
69
+
70
+ Learn more at https://www.plasmic.app/learn/other-assets/#icons
71
+ `
72
+ );
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,52 @@
1
+ import L from "lodash";
2
+
3
+ type StringGen = string | (() => string);
4
+
5
+ export function flatMap<T, U>(arr: T[], f: (x: T) => U[]) {
6
+ const r: U[] = [];
7
+ for (const x of arr) {
8
+ r.push(...f(x));
9
+ }
10
+ return r;
11
+ }
12
+
13
+ export function ensure<T>(x: T | null | undefined, msg: StringGen = ""): T {
14
+ if (x === null || x === undefined) {
15
+ debugger;
16
+ msg = (L.isString(msg) ? msg : msg()) || "";
17
+ throw new Error(
18
+ `Value must not be undefined or null${msg ? `- ${msg}` : ""}`
19
+ );
20
+ } else {
21
+ return x;
22
+ }
23
+ }
24
+
25
+ export function ensureString(x: any) {
26
+ if (L.isString(x)) {
27
+ return x;
28
+ } else {
29
+ throw new Error(`Expected ${x} to be a string`);
30
+ }
31
+ }
32
+
33
+ export class AssertionError extends Error {
34
+ constructor(msg = "Assertion failed") {
35
+ super(msg);
36
+ }
37
+ }
38
+
39
+ export function assert<T>(
40
+ cond: T,
41
+ msg: StringGen = "Assertion failed"
42
+ ): asserts cond {
43
+ if (!cond) {
44
+ // We always generate an non empty message so that it doesn't get swallowed
45
+ // by the async library.
46
+ msg = (L.isString(msg) ? msg : msg()) || "Assertion failed";
47
+ debugger;
48
+ throw new AssertionError(msg);
49
+ }
50
+ }
51
+
52
+ export const tuple = <T extends any[]>(...args: T): T => args;