@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,223 @@
1
+ import { spawnSync } from "child_process";
2
+ import glob from "fast-glob";
3
+ import findupSync from "findup-sync";
4
+ import latest from "latest-version";
5
+ import path from "path";
6
+ import semver from "semver";
7
+ import { logger } from "../deps";
8
+ import { PlasmicContext } from "./config-utils";
9
+ import { findFile, readFileText } from "./file-utils";
10
+ import { confirmWithUser } from "./user-utils";
11
+
12
+ export function getParsedCliPackageJson() {
13
+ const packageJson = findupSync("package.json", { cwd: __dirname });
14
+ if (!packageJson) {
15
+ throw new Error(`Cannot find package.json in ancestors of ${__dirname}`);
16
+ }
17
+ return parsePackageJson(packageJson);
18
+ }
19
+
20
+ export function getCliVersion() {
21
+ const j = getParsedCliPackageJson();
22
+ return j.version as string;
23
+ }
24
+
25
+ /**
26
+ * Call this to check if we match the engine policy
27
+ */
28
+ export function checkEngineStrict(): boolean {
29
+ const pkg = getParsedCliPackageJson();
30
+ const minNodeVersion = pkg?.engines?.node;
31
+ if (!!minNodeVersion && !semver.satisfies(process.version, minNodeVersion)) {
32
+ logger.warn(`Plasmic only works on Node ${minNodeVersion}`);
33
+ return false;
34
+ }
35
+ return true;
36
+ }
37
+
38
+ export function getParsedPackageJson() {
39
+ const packageJson = findupSync("package.json");
40
+ if (!packageJson) {
41
+ throw new Error(`Cannot find package.json`);
42
+ }
43
+ return parsePackageJson(packageJson);
44
+ }
45
+
46
+ // @TODO: is this function still used?
47
+ export async function warnLatest(
48
+ context: PlasmicContext,
49
+ pkg: string,
50
+ baseDir: string,
51
+ msgs: {
52
+ requiredMsg: () => string;
53
+ updateMsg: (curVersion: string, latestVersion: string) => string;
54
+ },
55
+ yes?: boolean
56
+ ) {
57
+ const check = await checkVersion(context, pkg);
58
+ if (check.type === "up-to-date") {
59
+ return;
60
+ } else if (check.type === "wrong-npm-registry") {
61
+ logger.warn(
62
+ `${msgs.requiredMsg()} Unable to find this package in your npm registry. Please update this dependency manually.`
63
+ );
64
+ return;
65
+ }
66
+
67
+ if (
68
+ await confirmWithUser(
69
+ `${
70
+ check.type === "not-installed"
71
+ ? msgs.requiredMsg()
72
+ : msgs.updateMsg(check.current, check.latest)
73
+ } Do you want to ${
74
+ check.type === "not-installed" ? "add" : "update"
75
+ } it now?`,
76
+ yes
77
+ )
78
+ ) {
79
+ installUpgrade(pkg, baseDir);
80
+ }
81
+ }
82
+
83
+ async function checkVersion(context: PlasmicContext, pkg: string) {
84
+ // Try to get the latest version from npm
85
+ let last = null;
86
+ try {
87
+ last = await latest(pkg);
88
+ } catch (e) {
89
+ // This is likely because .npmrc is set to a different registry
90
+ return { type: "wrong-npm-registry" } as const;
91
+ }
92
+
93
+ const cur = findInstalledVersion(context, pkg);
94
+ if (!cur) {
95
+ return { type: "not-installed" } as const;
96
+ }
97
+ if (semver.gt(last, cur)) {
98
+ return {
99
+ type: "obsolete",
100
+ latest: last,
101
+ current: cur,
102
+ } as const;
103
+ }
104
+ return { type: "up-to-date" } as const;
105
+ }
106
+
107
+ export function findInstalledVersion(context: PlasmicContext, pkg: string) {
108
+ const filename = findInstalledPackageJsonFile(context, pkg);
109
+ if (filename) {
110
+ const json = parsePackageJson(filename);
111
+ if (json && json.name === pkg) {
112
+ return json.version as string;
113
+ }
114
+ }
115
+ return undefined;
116
+ }
117
+
118
+ /**
119
+ * Detects if the cli is globally installed. `rootDir` is the folder
120
+ * where plasmic.json is
121
+ */
122
+ export function isCliGloballyInstalled(rootDir: string) {
123
+ const packageJsonFile = findPackageJsonPath(rootDir);
124
+ if (!packageJsonFile) {
125
+ // We assume global, as instructions state global and we can't really
126
+ // do better
127
+ return true;
128
+ }
129
+ const installedDir = __dirname;
130
+
131
+ // Else, we assume it is local if the installedDir is a subfolder of
132
+ // the root project dir
133
+ return !installedDir.startsWith(path.dirname(packageJsonFile));
134
+ }
135
+
136
+ function findPackageJsonPath(dir: string) {
137
+ return findFile(dir, (f) => f === "package.json", {
138
+ traverseParents: true,
139
+ });
140
+ }
141
+
142
+ export function findPackageJsonDir(rootDir: string) {
143
+ const filePath = findPackageJsonPath(rootDir);
144
+ return filePath ? path.dirname(filePath) : undefined;
145
+ }
146
+
147
+ function findInstalledPackageJsonFile(context: PlasmicContext, pkg: string) {
148
+ const packageJsonPath = findPackageJsonPath(context.rootDir);
149
+ const rootDir = packageJsonPath
150
+ ? path.dirname(packageJsonPath)
151
+ : context.rootDir;
152
+ const files = glob.sync(`${rootDir}/**/node_modules/${pkg}/package.json`);
153
+ return files.length > 0 ? files[0] : undefined;
154
+ }
155
+
156
+ function parsePackageJson(path: string) {
157
+ try {
158
+ return JSON.parse(readFileText(path));
159
+ } catch (e) {
160
+ return undefined;
161
+ }
162
+ }
163
+
164
+ export function installUpgrade(
165
+ pkg: string,
166
+ baseDir: string,
167
+ opts: { global?: boolean; dev?: boolean } = {}
168
+ ) {
169
+ const cmd = installCommand(pkg, baseDir, opts);
170
+ if (!process.env.QUIET) {
171
+ logger.info(cmd);
172
+ }
173
+ const r = spawnSync(cmd, {
174
+ shell: true,
175
+ stdio: process.env.QUIET ? "ignore" : "inherit",
176
+ cwd: baseDir,
177
+ });
178
+ if (r.status === 0) {
179
+ if (!process.env.QUIET) {
180
+ logger.info(`Successfully added ${pkg} dependency.`);
181
+ }
182
+ return true;
183
+ } else {
184
+ logger.warn(
185
+ `Cannot add ${pkg} to your project dependencies. Please add it manually.`
186
+ );
187
+ return false;
188
+ }
189
+ }
190
+
191
+ export function installCommand(
192
+ pkg: string,
193
+ baseDir: string,
194
+ opts: { global?: boolean; dev?: boolean } = {}
195
+ ) {
196
+ const mgr = detectPackageManager(baseDir);
197
+ if (mgr === "yarn") {
198
+ if (opts.global) {
199
+ return `yarn global add ${pkg}`;
200
+ } else if (opts.dev) {
201
+ return `yarn add --dev --ignore-scripts -W ${pkg}`;
202
+ } else {
203
+ return `yarn add --ignore-scripts -W ${pkg}`;
204
+ }
205
+ } else {
206
+ if (opts.global) {
207
+ return `npm install -g ${pkg}`;
208
+ } else if (opts.dev) {
209
+ return `npm install --save-dev --ignore-scripts ${pkg}`;
210
+ } else {
211
+ return `npm install --ignore-scripts ${pkg}`;
212
+ }
213
+ }
214
+ }
215
+
216
+ export function detectPackageManager(baseDir: string) {
217
+ const yarnLock = findupSync("yarn.lock", { cwd: baseDir });
218
+ if (yarnLock) {
219
+ return "yarn";
220
+ } else {
221
+ return "npm";
222
+ }
223
+ }
@@ -0,0 +1,22 @@
1
+ import inquirer from "inquirer";
2
+
3
+ export function askChoice<T>(question: {
4
+ message: string;
5
+ choices: T[];
6
+ defaultAnswer: T;
7
+ hidePrompt: boolean;
8
+ }) {
9
+ if (question.hidePrompt) {
10
+ return question.defaultAnswer;
11
+ }
12
+ return inquirer
13
+ .prompt([
14
+ {
15
+ name: "answer",
16
+ type: "list",
17
+ message: question.message,
18
+ choices: question.choices,
19
+ },
20
+ ])
21
+ .then((answer) => answer.answer as T);
22
+ }
@@ -0,0 +1,245 @@
1
+ import L from "lodash";
2
+ import { SyncArgs } from "../actions/sync";
3
+ import { ProjectVersionMeta, VersionResolution } from "../api";
4
+ import { logger } from "../deps";
5
+ import { CONFIG_FILE_NAME, PlasmicContext } from "./config-utils";
6
+ import { HandledError } from "./error";
7
+ import { ensure } from "./lang-utils";
8
+ import * as semver from "./semver";
9
+ import { confirmWithUser } from "./user-utils";
10
+
11
+ /**
12
+ * Starting at the root, do a BFS of the full dependency tree
13
+ * Because ProjectVersionMeta only stores the (projectId, version),
14
+ * we need to search for the full ProjectVersionMeta of dependencies from `available`
15
+ * @param root
16
+ * @param versionResolution
17
+ */
18
+ function walkDependencyTree(
19
+ root: ProjectVersionMeta,
20
+ available: ProjectVersionMeta[]
21
+ ): ProjectVersionMeta[] {
22
+ const queue: ProjectVersionMeta[] = [root];
23
+ const result: ProjectVersionMeta[] = [];
24
+
25
+ const getMeta = (projectId: string, version: string): ProjectVersionMeta => {
26
+ const meta = available.find(
27
+ (m) => m.projectId === projectId && m.version === version
28
+ );
29
+ if (!meta) {
30
+ throw new Error(
31
+ `Cannot find projectId=${projectId}, version=${version} in the sync resolution results.`
32
+ );
33
+ }
34
+ return meta;
35
+ };
36
+
37
+ while (queue.length > 0) {
38
+ const curr = ensure(queue.shift());
39
+ result.push(curr);
40
+ queue.push(
41
+ ...L.toPairs(curr.dependencies).map(([projectId, version]) =>
42
+ getMeta(projectId, version)
43
+ )
44
+ );
45
+ }
46
+
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * For a given project, check if its compatible with plasmic.json, plasmic.lock, and user
52
+ * @param meta
53
+ * @param context
54
+ */
55
+ async function checkProjectMeta(
56
+ meta: ProjectVersionMeta,
57
+ root: ProjectVersionMeta,
58
+ context: PlasmicContext,
59
+ opts: SyncArgs
60
+ ): Promise<boolean> {
61
+ const projectId = meta.projectId;
62
+ const projectName = meta.projectName;
63
+ const newVersion = meta.version;
64
+
65
+ // Checks newVersion against plasmic.lock
66
+ const checkVersionLock = async (): Promise<boolean> => {
67
+ const projectLock = context.lock.projects.find(
68
+ (p) => p.projectId === projectId
69
+ );
70
+ const versionOnDisk = projectLock?.version;
71
+
72
+ if (!versionOnDisk) {
73
+ // Always sync if we haven't seen sync'ed before
74
+ return true;
75
+ }
76
+
77
+ if (
78
+ semver.isLatest(versionOnDisk) &&
79
+ semver.isLatest(newVersion) &&
80
+ meta !== root
81
+ ) {
82
+ // If this is a dependency (not root), and we're dealing with latest dep version
83
+ // just skip, it's confusing
84
+ logger.warn(
85
+ `'${root.projectName}' depends on ${projectName}@${newVersion}. To update this project, explicitly specify this project for sync. Skipping...`
86
+ );
87
+ return false;
88
+ }
89
+
90
+ if (semver.isLatest(newVersion)) {
91
+ // Always sync when version set to "latest"
92
+ return true;
93
+ }
94
+
95
+ if (semver.isLatest(versionOnDisk)) {
96
+ // Explicitly allow downgrades from "latest" to published version
97
+ return true;
98
+ }
99
+
100
+ // At this point, we can assume newVersion is always X.Y.Z (not latest)
101
+ if (semver.eq(newVersion, versionOnDisk)) {
102
+ if (opts.force) {
103
+ logger.info(
104
+ `Project '${projectName}'@${newVersion} is already up to date, but syncing anyway because --force is used`
105
+ );
106
+ return true;
107
+ } else {
108
+ logger.info(
109
+ `Project '${projectName}'@${newVersion} is already up to date; skipping. (To force an update, run again with "--force")`
110
+ );
111
+ return false;
112
+ }
113
+ }
114
+
115
+ if (semver.lt(newVersion, versionOnDisk)) {
116
+ meta === root
117
+ ? logger.warn(
118
+ `The local version of '${projectName}' (${versionOnDisk}) is higher than requested version @${newVersion}. Plasmic does not support downgrading a project. You should consider updating the version range in ${CONFIG_FILE_NAME}.`
119
+ )
120
+ : logger.warn(
121
+ `'${root.projectName}' uses '${projectName}'@${newVersion}, but your code has '${projectName}'@${versionOnDisk}. You should consider upgrading this dependency in Plasmic Studio.`
122
+ );
123
+ return false;
124
+ }
125
+
126
+ if (semver.gt(newVersion, versionOnDisk)) {
127
+ if (meta === root) {
128
+ return true;
129
+ } else {
130
+ logger.info(
131
+ `'${root.projectName}' uses '${projectName}'@${newVersion}, but your code has version ${versionOnDisk}`
132
+ );
133
+ return await confirmWithUser(
134
+ `Do you want to upgrade '${projectName}' to ${newVersion}?`,
135
+ opts.yes
136
+ );
137
+ }
138
+ }
139
+
140
+ throw new Error(
141
+ `Error comparing version=${newVersion} with the version found in plasmic.lock (${versionOnDisk}) for '${projectName}'`
142
+ );
143
+ };
144
+
145
+ // Checks newVersion against plasmic.json
146
+ const checkVersionRange = async (): Promise<boolean> => {
147
+ const projectConfig = context.config.projects.find(
148
+ (p) => p.projectId === projectId
149
+ );
150
+ const versionRange = projectConfig?.version;
151
+ // If haven't seen this before
152
+ if (!versionRange) {
153
+ // Always sync down dependencies if it's the first time to avoid compile/fix-imports error
154
+ projectId !== root.projectId
155
+ ? logger.info(
156
+ `'${root.projectName}' uses '${projectName}', which has never been synced before. We will also sync '${projectName}'@${newVersion}.`
157
+ )
158
+ : logger.info(
159
+ `'${projectName} has never been synced before. Syncing...'`
160
+ );
161
+ return true;
162
+ }
163
+
164
+ // If satisfies range in plasmic.json
165
+ if (semver.satisfies(newVersion, versionRange)) {
166
+ logger.info(`Updating project '${projectName}' to ${newVersion}`);
167
+ return true;
168
+ }
169
+
170
+ logger.warn(
171
+ `${projectName}@${newVersion} falls outside the range specified in ${CONFIG_FILE_NAME} (${versionRange})\nTip: To avoid this warning in the future, update your ${CONFIG_FILE_NAME}.`
172
+ );
173
+ return await confirmWithUser(
174
+ "Do you want to force it?",
175
+ opts.force || opts.yes,
176
+ "n"
177
+ );
178
+ };
179
+
180
+ const projectIds =
181
+ opts.projects.length > 0
182
+ ? opts.projects
183
+ : context.config.projects.map((p) => p.projectId);
184
+
185
+ if (projectIds.includes(projectId) && opts.force) {
186
+ // if --force is used, and this is in the list of projects to sync, then
187
+ // we should always sync it, even if nothing has changed
188
+ return true;
189
+ }
190
+
191
+ return (await checkVersionLock()) && (await checkVersionRange());
192
+ }
193
+
194
+ /**
195
+ * Checks the versionResolution with plasmic.json, plasmic.lock, and user prompts
196
+ * to compute which projects should be synced
197
+ * @param versionResolution
198
+ * @param context
199
+ */
200
+ export async function checkVersionResolution(
201
+ versionResolution: VersionResolution,
202
+ context: PlasmicContext,
203
+ opts: SyncArgs
204
+ ): Promise<ProjectVersionMeta[]> {
205
+ // Fail if there's nothing to sync
206
+ if (versionResolution.projects.length <= 0) {
207
+ throw new HandledError(
208
+ `Found nothing to sync. Make sure the projectId and version values are valid in ${CONFIG_FILE_NAME}.`
209
+ );
210
+ }
211
+
212
+ const seen: ProjectVersionMeta[] = [];
213
+ const result: ProjectVersionMeta[] = [];
214
+ for (const root of versionResolution.projects) {
215
+ const queue = opts.nonRecursive
216
+ ? [root]
217
+ : walkDependencyTree(root, versionResolution.dependencies).reverse();
218
+ for (const m of queue) {
219
+ // If we haven't seen this yet
220
+ if (!seen.find((p) => p.projectId === m.projectId)) {
221
+ if (await checkProjectMeta(m, root, context, opts)) {
222
+ result.push(m);
223
+ }
224
+ seen.push(m);
225
+ }
226
+ }
227
+ }
228
+
229
+ // Ignore repeats
230
+ return result;
231
+ }
232
+
233
+ export function getDependencies(
234
+ projectId: string,
235
+ version: string,
236
+ versionResolution: VersionResolution
237
+ ) {
238
+ const filterFn = (m: ProjectVersionMeta) =>
239
+ m.projectId === projectId && m.version === version;
240
+ const meta =
241
+ versionResolution.projects.find(filterFn) ??
242
+ versionResolution.dependencies.find(filterFn);
243
+
244
+ return meta?.dependencies;
245
+ }
@@ -0,0 +1,67 @@
1
+ import L from "lodash";
2
+ import * as semverlib from "semver";
3
+
4
+ /**
5
+ * Wrap `semver` with support for understanding "latest"
6
+ * - "latest" is both a version and a range
7
+ * - "latest" range will match any valid version number
8
+ * - "latest" version will only match "latest" version range
9
+ **/
10
+
11
+ export type Version = semverlib.SemVer | "latest";
12
+ export const latestTag = "latest";
13
+ export const isLatest = (v: string) => v === latestTag;
14
+ export const valid = (v: string) =>
15
+ isLatest(v) ? latestTag : semverlib.valid(v);
16
+ export const inc = (v: string, release: semverlib.ReleaseType) =>
17
+ isLatest(v) ? latestTag : semverlib.inc(v, release);
18
+ export const prerelease = (v: string) =>
19
+ isLatest(v) ? [] : semverlib.prerelease(v);
20
+ export const major = (v: string) =>
21
+ isLatest(v) ? latestTag : semverlib.major(v);
22
+ export const minor = (v: string) =>
23
+ isLatest(v) ? latestTag : semverlib.minor(v);
24
+ export const patch = (v: string) =>
25
+ isLatest(v) ? latestTag : semverlib.patch(v);
26
+ export const eq = (v1: string, v2: string) =>
27
+ (isLatest(v1) && isLatest(v2)) ||
28
+ (!isLatest(v1) && !isLatest(v2) && semverlib.eq(v1, v2));
29
+ export const gt = (v1: string, v2: string) =>
30
+ (isLatest(v1) && !isLatest(v2)) ||
31
+ (!isLatest(v1) && !isLatest(v2) && semverlib.gt(v1, v2));
32
+ export const lt = (v1: string, v2: string) =>
33
+ (!isLatest(v1) && isLatest(v2)) ||
34
+ (!isLatest(v1) && !isLatest(v2) && semverlib.lt(v1, v2));
35
+ export const validRange = (range: string) =>
36
+ isLatest(range) ? latestTag : semverlib.validRange(range);
37
+ export const satisfies = (v: string, range: string) =>
38
+ (isLatest(range) && !!valid(v)) ||
39
+ (!isLatest(v) && !isLatest(range) && semverlib.satisfies(v, range));
40
+ export const toTildeRange = (v: string) =>
41
+ isLatest(v) ? latestTag : !!semverlib.valid(v) ? "~" + v : null;
42
+ export const toCaretRange = (v: string) =>
43
+ isLatest(v) ? latestTag : !!semverlib.valid(v) ? "^" + v : null;
44
+ export const gte = (v1: string, v2: string) => eq(v1, v2) || gt(v1, v2);
45
+ export const lte = (v1: string, v2: string) => eq(v1, v2) || lt(v1, v2);
46
+ export const neq = (v1: string, v2: string) => !eq(v1, v2);
47
+ export const sortAsc = (versions: string[]) =>
48
+ L.cloneDeep(versions).sort((v1, v2) =>
49
+ gt(v1, v2) ? +1 : eq(v1, v2) ? 0 : -1
50
+ );
51
+ export const sortDesc = (versions: string[]) => sortAsc(versions).reverse();
52
+ export const minSatisfying = (versions: string[], range: string) =>
53
+ sortAsc(versions).find((v) => satisfies(v, range)) ?? null;
54
+ export const maxSatisfying = (versions: string[], range: string) =>
55
+ sortDesc(versions).find((v) => satisfies(v, range)) ?? null;
56
+ export const coerce = (v: string) =>
57
+ isLatest(v) ? latestTag : semverlib.coerce(v)?.version;
58
+ export const gtr = (version: string, range: string) =>
59
+ (isLatest(version) && !isLatest(range)) ||
60
+ (!isLatest(version) && !isLatest(range) && semverlib.gtr(version, range));
61
+ export const ltr = (version: string, range: string) =>
62
+ (!isLatest(version) && isLatest(range)) ||
63
+ (!isLatest(version) && !isLatest(range) && semverlib.ltr(version, range));
64
+ export const outside = (version: string, range: string, hilo?: ">" | "<") =>
65
+ (hilo === ">" && gtr(version, range)) ||
66
+ (hilo === "<" && ltr(version, range)) ||
67
+ (!hilo && (gtr(version, range) || ltr(version, range)));
@@ -0,0 +1,116 @@
1
+ import fs from "fs";
2
+ import * as path from "path";
3
+ import * as tmp from "tmp";
4
+ import {
5
+ AuthConfig,
6
+ AUTH_FILE_NAME,
7
+ CONFIG_FILE_NAME,
8
+ LOADER_CONFIG_FILE_NAME,
9
+ PlasmicConfig,
10
+ PlasmicLoaderConfig,
11
+ } from "../utils/config-utils";
12
+ import { deleteFileBuffered, readFileText, writeFileText } from "./file-utils";
13
+
14
+ export class TempRepo {
15
+ tmpDir: tmp.DirResult; // Temporary directory used for tests
16
+
17
+ constructor() {
18
+ this.tmpDir = tmp.dirSync({ unsafeCleanup: true });
19
+ }
20
+
21
+ destroy() {
22
+ this.tmpDir.removeCallback();
23
+ }
24
+
25
+ resolveFile(relativePath: string): string {
26
+ return path.resolve(this.tmpDir.name, relativePath);
27
+ }
28
+
29
+ readFile(relativePath: string): string {
30
+ const absPath = this.resolveFile(relativePath);
31
+ const buf = readFileText(absPath);
32
+ return buf.toString();
33
+ }
34
+
35
+ writeFile(relativePath: string, data: string) {
36
+ const absPath = this.resolveFile(relativePath);
37
+ writeFileText(absPath, data);
38
+ }
39
+
40
+ deleteFile(relativePath: string) {
41
+ const absPath = this.resolveFile(relativePath);
42
+ deleteFileBuffered(absPath);
43
+ }
44
+
45
+ checkFile(relativePath: string): boolean {
46
+ const absPath = this.resolveFile(relativePath);
47
+ try {
48
+ const stats = fs.statSync(absPath);
49
+ return !!stats ? true : false;
50
+ } catch (e) {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ getComponentFileContents(
56
+ projectId: string,
57
+ componentId: string
58
+ ): string | undefined {
59
+ const plasmicJson: PlasmicConfig = JSON.parse(
60
+ this.readFile(CONFIG_FILE_NAME)
61
+ );
62
+ const srcDir = plasmicJson.srcDir;
63
+ const projectConfig = plasmicJson.projects.find(
64
+ (p) => p.projectId === projectId
65
+ );
66
+ if (!projectConfig) {
67
+ return;
68
+ }
69
+ const componentConfig = projectConfig.components.find(
70
+ (c) => c.id === componentId
71
+ );
72
+ if (!componentConfig) {
73
+ return;
74
+ }
75
+ const data = this.readFile(
76
+ path.join(srcDir, componentConfig.renderModuleFilePath)
77
+ );
78
+ return data;
79
+ }
80
+
81
+ plasmicAuthPath(): string {
82
+ return this.resolveFile(AUTH_FILE_NAME);
83
+ }
84
+
85
+ writePlasmicAuth(json: AuthConfig) {
86
+ this.writeFile(AUTH_FILE_NAME, JSON.stringify(json));
87
+ }
88
+
89
+ deletePlasmicAuth() {
90
+ this.deleteFile(AUTH_FILE_NAME);
91
+ }
92
+
93
+ plasmicJsonPath(): string {
94
+ return this.resolveFile(CONFIG_FILE_NAME);
95
+ }
96
+
97
+ readPlasmicJson(): PlasmicConfig {
98
+ return JSON.parse(this.readFile(CONFIG_FILE_NAME));
99
+ }
100
+
101
+ writePlasmicJson(json: PlasmicConfig) {
102
+ this.writeFile(CONFIG_FILE_NAME, JSON.stringify(json));
103
+ }
104
+
105
+ deletePlasmicJson() {
106
+ this.deleteFile(CONFIG_FILE_NAME);
107
+ }
108
+
109
+ plasmicLoaderJsonPath() {
110
+ return this.resolveFile(LOADER_CONFIG_FILE_NAME);
111
+ }
112
+
113
+ readPlasmicLoaderJson(): PlasmicLoaderConfig {
114
+ return JSON.parse(this.readFile(LOADER_CONFIG_FILE_NAME));
115
+ }
116
+ }
@@ -0,0 +1,37 @@
1
+ import inquirer from "inquirer";
2
+ import { logger } from "../deps";
3
+
4
+ /**
5
+ * Provide a standardized way to ask user to continue
6
+ * @param message
7
+ * @param yes - If true, always return true without prompting.
8
+ * @param default - Override the default value returned if the user presses enter
9
+ */
10
+ export async function confirmWithUser(
11
+ message: string,
12
+ yes?: boolean,
13
+ defaultAnswer?: "y" | "n"
14
+ ): Promise<boolean> {
15
+ if (process.env.QUIET) {
16
+ return true;
17
+ }
18
+
19
+ if (!!yes) {
20
+ if (!process.env.QUIET) {
21
+ logger.info(`${message} (Y/n): y`);
22
+ }
23
+ return true;
24
+ }
25
+
26
+ defaultAnswer = defaultAnswer ?? "y";
27
+ const isDefaultYes = defaultAnswer === "y";
28
+ const choices = `(${isDefaultYes ? "Y" : "y"}/${isDefaultYes ? "n" : "N"})`;
29
+ const res = await inquirer.prompt([
30
+ {
31
+ name: "continue",
32
+ message: `${message} ${choices}`,
33
+ default: defaultAnswer,
34
+ },
35
+ ]);
36
+ return ["y", "yes"].includes(res.continue.toLowerCase());
37
+ }