@plasmicapp/cli 0.1.163 → 0.1.167

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.
@@ -50,7 +50,7 @@ function mockProjectToProjectVersionMeta(mock, componentIdOrNames) {
50
50
  .filter((c) => !componentIdOrNames ||
51
51
  componentIdOrNames.includes(c.name) ||
52
52
  componentIdOrNames.includes(c.id))
53
- .map((c) => c.id) });
53
+ .map((c) => c.id), indirect: false });
54
54
  }
55
55
  /**
56
56
  * Call this in test to setup the data model
@@ -250,6 +250,8 @@ class PlasmicApi {
250
250
  globalVariantChecksums: [],
251
251
  projectCssChecksum: "",
252
252
  },
253
+ usedNpmPackages: [],
254
+ externalCssImports: [],
253
255
  };
254
256
  return result;
255
257
  });
@@ -51,20 +51,20 @@ describe("Project API tokens", () => {
51
51
  fixtures_1.tmpRepo.writePlasmicJson(Object.assign(Object.assign({}, fixtures_1.defaultPlasmicJson), { projects: [Object.assign(Object.assign({}, fixtures_1.project1Config), { projectApiToken: "blah" })] }));
52
52
  yield expect(sync_1.sync(fixtures_1.opts)).resolves.toBeUndefined();
53
53
  fixtures_1.expectProject1Components();
54
- fixtures_1.expectProject1PlasmicJson();
54
+ fixtures_1.expectProject1PlasmicJson({ projectApiToken: true });
55
55
  // Re-run, this time with no auth.
56
56
  removeAuth();
57
- yield expect(sync_1.sync(fixtures_1.opts)).resolves.toBeUndefined();
57
+ yield expect(sync_1.sync(fixtures_1.opts)).rejects.toThrow("No user+token, and project API tokens don't match");
58
58
  }));
59
59
  test("is filled in by auth'd user if project exists but token was initially missing", () => __awaiter(void 0, void 0, void 0, function* () {
60
60
  fixtures_1.opts.projects = ["projectId1"];
61
61
  fixtures_1.tmpRepo.writePlasmicJson(Object.assign(Object.assign({}, fixtures_1.defaultPlasmicJson), { projects: [fixtures_1.project1Config] }));
62
62
  yield expect(sync_1.sync(fixtures_1.opts)).resolves.toBeUndefined();
63
63
  fixtures_1.expectProject1Components();
64
- fixtures_1.expectProject1PlasmicJson();
64
+ fixtures_1.expectProject1PlasmicJson({ projectApiToken: true });
65
65
  // Re-run, this time with no auth.
66
66
  removeAuth();
67
- yield expect(sync_1.sync(fixtures_1.opts)).resolves.toBeUndefined();
67
+ yield expect(sync_1.sync(fixtures_1.opts)).rejects.toThrow("Unable to authenticate Plasmic. Please run 'plasmic auth' or check the projectApiTokens in your plasmic.json, and try again.");
68
68
  }));
69
69
  test("when not available, should prompt for auth", () => __awaiter(void 0, void 0, void 0, function* () {
70
70
  fixtures_1.opts.projects = ["projectId1"];
@@ -82,14 +82,14 @@ function ensureRequiredPackages(context, baseDir, yes) {
82
82
  // Exit so the user can run again with the new cli
83
83
  throw new error_1.HandledError();
84
84
  }
85
- const reactWebVersion = npm_utils_1.findInstalledVersion(context, "@plasmicapp/react-web");
85
+ const reactWebVersion = npm_utils_1.findInstalledVersion(context, baseDir, "@plasmicapp/react-web");
86
86
  if (!reactWebVersion ||
87
87
  semver.gt(requireds["@plasmicapp/react-web"], reactWebVersion)) {
88
88
  yield confirmInstall("@plasmicapp/react-web", requireds["@plasmicapp/react-web"], { global: false, dev: false });
89
89
  }
90
90
  if (context.config.code.reactRuntime === "automatic") {
91
91
  // Using automatic runtime requires installing the @plasmicapp/react-web-runtime package
92
- const runtimeVersion = npm_utils_1.findInstalledVersion(context, "@plasmicapp/react-web-runtime");
92
+ const runtimeVersion = npm_utils_1.findInstalledVersion(context, baseDir, "@plasmicapp/react-web-runtime");
93
93
  if (!runtimeVersion ||
94
94
  semver.gt(requireds["@plasmicapp/react-web-runtime"], runtimeVersion)) {
95
95
  yield confirmInstall("@plasmicapp/react-web-runtime", requireds["@plasmicapp/react-web-runtime"], { global: false, dev: false });
@@ -150,6 +150,7 @@ function sync(opts, metadataDefaults) {
150
150
  versionRange: versionRange || ((_a = projectConfigMap[projectId]) === null || _a === void 0 ? void 0 : _a.version) || "latest",
151
151
  componentIdOrNames: undefined,
152
152
  projectApiToken: projectApiToken || projectIdToToken.get(projectId),
153
+ indirect: false,
153
154
  };
154
155
  });
155
156
  const projectSyncParams = projectWithVersion.length
@@ -159,6 +160,7 @@ function sync(opts, metadataDefaults) {
159
160
  versionRange: p.version,
160
161
  componentIdOrNames: undefined,
161
162
  projectApiToken: p.projectApiToken,
163
+ indirect: !!p.indirect,
162
164
  }));
163
165
  // Short-circuit if nothing to sync
164
166
  if (projectSyncParams.length === 0) {
@@ -206,13 +208,15 @@ function sync(opts, metadataDefaults) {
206
208
  ...versionResolution.dependencies,
207
209
  ].map((p) => lodash_1.default.pick(p, "projectId", "projectApiToken"));
208
210
  context.api.attachProjectIdsAndTokens(projectIdsAndTokens);
211
+ const externalNpmPackages = new Set();
212
+ const externalCssImports = new Set();
209
213
  // Perform the actual sync
210
214
  yield file_utils_1.withBufferedFs(() => __awaiter(this, void 0, void 0, function* () {
211
215
  var _b;
212
216
  // Sync in sequence (no parallelism)
213
217
  // going in reverse to get leaves of the dependency tree first
214
218
  for (const projectMeta of projectsToSync) {
215
- yield syncProject(context, opts, projectIdsAndTokens, projectMeta.projectId, projectMeta.componentIds, projectMeta.version, projectMeta.dependencies, summary, pendingMerge, metadataDefaults);
219
+ yield syncProject(context, opts, projectIdsAndTokens, projectMeta.projectId, projectMeta.componentIds, projectMeta.version, projectMeta.dependencies, summary, pendingMerge, projectMeta.indirect, externalNpmPackages, externalCssImports, metadataDefaults);
216
220
  }
217
221
  // Materialize scheme into each component config.
218
222
  context.config.projects.forEach((p) => p.components.forEach((c) => {
@@ -221,11 +225,20 @@ function sync(opts, metadataDefaults) {
221
225
  }
222
226
  }));
223
227
  yield syncStyleConfig(context, yield context.api.genStyleConfig(context.config.style));
224
- // Update project version if specified and successfully synced.
228
+ // Update project version and indirect status if specified and
229
+ // successfully synced.
225
230
  if (projectWithVersion.length) {
226
231
  const versionMap = {};
227
232
  projectWithVersion.forEach((p) => (versionMap[p.projectId] = p.versionRange));
228
- context.config.projects.forEach((p) => (p.version = versionMap[p.projectId] || p.version));
233
+ const indirectMap = {};
234
+ projectsToSync.forEach((p) => (indirectMap[p.projectId] = p.indirect));
235
+ context.config.projects.forEach((p) => {
236
+ p.version = versionMap[p.projectId] || p.version;
237
+ // Only update `indirect` if it is set in current config.
238
+ if (p.projectId in indirectMap && p.indirect) {
239
+ p.indirect = indirectMap[p.projectId];
240
+ }
241
+ });
229
242
  }
230
243
  // Fix imports
231
244
  const fixImportContext = code_utils_1.mkFixImportContext(context.config);
@@ -247,6 +260,13 @@ function sync(opts, metadataDefaults) {
247
260
  // Write the new ComponentConfigs to disk
248
261
  yield config_utils_1.updateConfig(context, context.config, baseDir);
249
262
  }));
263
+ yield checkExternalPkgs(context, baseDir, opts, Array.from(externalNpmPackages.keys()));
264
+ if (!opts.quiet && externalCssImports.size > 0) {
265
+ deps_1.logger.info(`This project uses external packages and styles. Make sure to import the following global CSS: ` +
266
+ Array.from(externalCssImports.keys())
267
+ .map((stmt) => `"${stmt}"`)
268
+ .join(", "));
269
+ }
250
270
  // Post-sync commands
251
271
  if (!opts.ignorePostSync) {
252
272
  for (const cmd of context.config.postSyncCommands || []) {
@@ -261,6 +281,20 @@ function sync(opts, metadataDefaults) {
261
281
  });
262
282
  }
263
283
  exports.sync = sync;
284
+ function checkExternalPkgs(context, baseDir, opts, pkgs) {
285
+ return __awaiter(this, void 0, void 0, function* () {
286
+ const missingPkgs = pkgs.filter((pkg) => {
287
+ const installedPkg = npm_utils_1.findInstalledVersion(context, baseDir, pkg);
288
+ return !installedPkg;
289
+ });
290
+ if (missingPkgs.length > 0) {
291
+ const upgrade = yield user_utils_1.confirmWithUser(`The following packages aren't installed but are required by some projects, would you like to install them? ${missingPkgs.join(", ")}`, opts.yes);
292
+ if (upgrade) {
293
+ npm_utils_1.installUpgrade(missingPkgs.join(" "), baseDir);
294
+ }
295
+ }
296
+ });
297
+ }
264
298
  function maybeRenamePathExt(context, path, ext) {
265
299
  if (!path) {
266
300
  return path;
@@ -281,7 +315,7 @@ function fixFileExtension(context) {
281
315
  });
282
316
  });
283
317
  }
284
- function syncProject(context, opts, projectIdsAndTokens, projectId, componentIds, projectVersion, dependencies, summary, pendingMerge, metadataDefaults) {
318
+ function syncProject(context, opts, projectIdsAndTokens, projectId, componentIds, projectVersion, dependencies, summary, pendingMerge, indirect, externalNpmPackages, externalCssImports, metadataDefaults) {
285
319
  var _a;
286
320
  return __awaiter(this, void 0, void 0, function* () {
287
321
  const newComponentScheme = opts.newComponentScheme || context.config.code.scheme;
@@ -301,6 +335,7 @@ function syncProject(context, opts, projectIdsAndTokens, projectId, componentIds
301
335
  checksums: existingChecksums,
302
336
  codeOpts: context.config.code,
303
337
  metadata: get_context_1.generateMetadata(Object.assign(Object.assign({}, metadataDefaults), { platform: context.config.platform }), opts.metadata),
338
+ indirect,
304
339
  });
305
340
  // Convert from TSX => JSX
306
341
  if (context.config.code.lang === "js") {
@@ -319,11 +354,13 @@ function syncProject(context, opts, projectIdsAndTokens, projectId, componentIds
319
354
  });
320
355
  }
321
356
  yield sync_global_variants_1.syncGlobalVariants(context, projectBundle.projectConfig, projectBundle.globalVariants, projectBundle.checksums, opts.baseDir);
357
+ yield syncProjectConfig(context, projectBundle.projectConfig, projectApiToken, projectVersion, dependencies, projectBundle.components, opts.forceOverwrite, !!opts.appendJsxOnMissingBase, summary, pendingMerge, projectBundle.checksums, opts.baseDir, indirect);
322
358
  syncCodeComponentsMeta(context, projectId, projectBundle.codeComponentMetas);
323
- yield syncProjectConfig(context, projectBundle.projectConfig, projectApiToken, projectVersion, dependencies, projectBundle.components, opts.forceOverwrite, !!opts.appendJsxOnMissingBase, summary, pendingMerge, projectBundle.checksums, opts.baseDir);
324
359
  yield sync_styles_1.upsertStyleTokens(context, projectBundle.usedTokens);
325
360
  yield sync_icons_1.syncProjectIconAssets(context, projectId, projectVersion, projectBundle.iconAssets, projectBundle.checksums, opts.baseDir);
326
361
  yield sync_images_1.syncProjectImageAssets(context, projectId, projectVersion, projectBundle.imageAssets, projectBundle.checksums);
362
+ (projectBundle.usedNpmPackages || []).forEach((pkg) => externalNpmPackages.add(pkg));
363
+ (projectBundle.externalCssImports || []).forEach((css) => externalCssImports.add(css));
327
364
  });
328
365
  }
329
366
  function syncStyleConfig(context, response) {
@@ -336,25 +373,23 @@ function syncStyleConfig(context, response) {
336
373
  });
337
374
  });
338
375
  }
339
- function syncProjectConfig(context, projectBundle, projectApiToken, version, dependencies, componentBundles, forceOverwrite, appendJsxOnMissingBase, summary, pendingMerge, checksums, baseDir) {
376
+ function syncProjectConfig(context, projectBundle, projectApiToken, version, dependencies, componentBundles, forceOverwrite, appendJsxOnMissingBase, summary, pendingMerge, checksums, baseDir, indirect) {
340
377
  return __awaiter(this, void 0, void 0, function* () {
341
378
  const defaultCssFilePath = file_utils_1.defaultResourcePath(context, projectBundle.projectName, projectBundle.cssFileName);
342
379
  const isNew = !context.config.projects.find((p) => p.projectId === projectBundle.projectId);
343
- // If latest, use that as the range, otherwise set to latest published (>=0.0.0)
344
- const versionRange = semver.isLatest(version) ? version : ">=0.0.0";
345
380
  const projectConfig = config_utils_1.getOrAddProjectConfig(context, projectBundle.projectId, config_utils_1.createProjectConfig({
346
381
  projectId: projectBundle.projectId,
347
382
  projectApiToken,
348
383
  projectName: projectBundle.projectName,
349
- version: versionRange,
384
+ version,
350
385
  cssFilePath: defaultCssFilePath,
386
+ indirect,
351
387
  }));
352
388
  // Update missing/outdated props
353
389
  projectConfig.projectName = projectBundle.projectName;
354
390
  if (!projectConfig.cssFilePath) {
355
391
  projectConfig.cssFilePath = defaultCssFilePath;
356
392
  }
357
- projectConfig.projectApiToken = projectApiToken;
358
393
  // plasmic.lock
359
394
  const projectLock = config_utils_1.getOrAddProjectLock(context, projectConfig.projectId);
360
395
  projectLock.version = version;
package/dist/api.d.ts CHANGED
@@ -58,6 +58,7 @@ export interface ProjectVersionMeta {
58
58
  dependencies: {
59
59
  [projectId: string]: string;
60
60
  };
61
+ indirect: boolean;
61
62
  }
62
63
  export interface VersionResolution {
63
64
  projects: ProjectVersionMeta[];
@@ -79,6 +80,8 @@ export interface ProjectBundle {
79
80
  iconAssets: IconBundle[];
80
81
  imageAssets: ImageBundle[];
81
82
  checksums: ChecksumBundle;
83
+ usedNpmPackages: string[];
84
+ externalCssImports: string[];
82
85
  }
83
86
  export declare type ProjectMeta = Omit<ProjectBundle, "projectConfig">;
84
87
  export interface StyleConfigResponse {
@@ -165,6 +168,7 @@ export declare class PlasmicApi {
165
168
  stylesOpts: StyleConfig;
166
169
  codeOpts: CodeConfig;
167
170
  checksums: ChecksumBundle;
171
+ indirect: boolean;
168
172
  metadata?: Metadata;
169
173
  }): Promise<ProjectBundle>;
170
174
  uploadBundle(projectId: string, bundleName: string, bundleJs: string, css: string[], metaJson: string, genModulePath: string | undefined, genCssPaths: string[], pkgVersion: string | undefined, extraPropMetaJson: string | undefined, themeProviderWrapper: string | undefined, themeModule: string | undefined): Promise<StyleTokensMap>;
@@ -0,0 +1,2 @@
1
+ import { PlasmicConfig } from "../utils/config-utils";
2
+ export declare function ensureIndirect(config: PlasmicConfig): PlasmicConfig;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureIndirect = void 0;
4
+ function ensureIndirect(config) {
5
+ for (const p of config.projects) {
6
+ if (p.indirect === undefined) {
7
+ p.indirect = false;
8
+ }
9
+ }
10
+ return config;
11
+ }
12
+ exports.ensureIndirect = ensureIndirect;
@@ -40,6 +40,7 @@ const npm_utils_1 = require("../utils/npm-utils");
40
40
  const user_utils_1 = require("../utils/user-utils");
41
41
  const _0_1_110_fileLocks_1 = require("./0.1.110-fileLocks");
42
42
  const _0_1_146_addReactRuntime_1 = require("./0.1.146-addReactRuntime");
43
+ const _0_1_166_indirect_1 = require("./0.1.166-indirect");
43
44
  const _0_1_27_migrateInit_1 = require("./0.1.27-migrateInit");
44
45
  const _0_1_28_tsToTsx_1 = require("./0.1.28-tsToTsx");
45
46
  const _0_1_31_ensureProjectIcons_1 = require("./0.1.31-ensureProjectIcons");
@@ -56,6 +57,7 @@ exports.MIGRATIONS = {
56
57
  "0.1.64": _0_1_64_imageFiles_1.ensureImageFiles,
57
58
  "0.1.95": _0_1_95_componentType_1.ensureComponentType,
58
59
  "0.1.146": _0_1_146_addReactRuntime_1.ensureReactRuntime,
60
+ "0.1.166": _0_1_166_indirect_1.ensureIndirect,
59
61
  };
60
62
  exports.LOCK_MIGRATIONS = {
61
63
  "0.1.110": _0_1_110_fileLocks_1.ensureFileLocks,
@@ -296,6 +296,10 @@
296
296
  },
297
297
  "type": "array"
298
298
  },
299
+ "indirect": {
300
+ "description": "True if the project was installed indirectly (as a dependency); if set,\ncodegen will not generate pages.",
301
+ "type": "boolean"
302
+ },
299
303
  "jsBundleThemes": {
300
304
  "items": {
301
305
  "$ref": "#/definitions/JsBundleThemeConfig"
@@ -324,6 +328,7 @@
324
328
  "cssFilePath",
325
329
  "icons",
326
330
  "images",
331
+ "indirect",
327
332
  "projectId",
328
333
  "projectName",
329
334
  "version"
@@ -9,5 +9,7 @@ export declare function standardTestSetup(includeDep?: boolean): void;
9
9
  export declare function standardTestTeardown(): void;
10
10
  export declare function expectProject1Components(): void;
11
11
  export declare const project1Config: ProjectConfig;
12
- export declare function expectProject1PlasmicJson(): void;
12
+ export declare function expectProject1PlasmicJson(optional?: {
13
+ [k in keyof ProjectConfig]?: boolean;
14
+ }): void;
13
15
  export declare function expectProjectAndDepPlasmicJson(): void;
@@ -138,12 +138,15 @@ exports.project1Config = {
138
138
  icons: [],
139
139
  images: [],
140
140
  jsBundleThemes: [],
141
+ indirect: false,
141
142
  };
142
- function expectProject1PlasmicJson() {
143
+ function expectProject1PlasmicJson(optional) {
143
144
  const plasmicJson = exports.tmpRepo.readPlasmicJson();
144
145
  expect(plasmicJson.projects.length).toEqual(1);
145
146
  const projectConfig = plasmicJson.projects[0];
146
- expect(projectConfig.projectApiToken).toBe("abc");
147
+ if (!(optional === null || optional === void 0 ? void 0 : optional.projectApiToken)) {
148
+ expect(projectConfig.projectApiToken).toBe("abc");
149
+ }
147
150
  expect(projectConfig.components.length).toEqual(2);
148
151
  const componentNames = projectConfig.components.map((c) => c.name);
149
152
  expect(componentNames).toContain("Button");
@@ -126,6 +126,11 @@ export interface ProjectConfig {
126
126
  icons: IconConfig[];
127
127
  /** Metadata for each synced image in this project */
128
128
  images: ImageConfig[];
129
+ /**
130
+ * True if the project was installed indirectly (as a dependency); if set,
131
+ * codegen will not generate pages.
132
+ */
133
+ indirect: boolean;
129
134
  }
130
135
  export declare function createProjectConfig(base: {
131
136
  projectId: string;
@@ -133,6 +138,7 @@ export declare function createProjectConfig(base: {
133
138
  projectName: string;
134
139
  version: string;
135
140
  cssFilePath: string;
141
+ indirect: boolean;
136
142
  }): ProjectConfig;
137
143
  export interface TokensConfig {
138
144
  scheme: "theo";
@@ -39,6 +39,7 @@ function createProjectConfig(base) {
39
39
  components: [],
40
40
  icons: [],
41
41
  images: [],
42
+ indirect: base.indirect,
42
43
  };
43
44
  }
44
45
  exports.createProjectConfig = createProjectConfig;
@@ -148,6 +149,7 @@ function getOrAddProjectConfig(context, projectId, base // if one doesn't exist,
148
149
  icons: [],
149
150
  images: [],
150
151
  jsBundleThemes: [],
152
+ indirect: false,
151
153
  };
152
154
  context.config.projects.push(project);
153
155
  }
@@ -10,7 +10,7 @@ export declare function warnLatest(context: PlasmicContext, pkg: string, baseDir
10
10
  requiredMsg: () => string;
11
11
  updateMsg: (curVersion: string, latestVersion: string) => string;
12
12
  }, yes?: boolean): Promise<void>;
13
- export declare function findInstalledVersion(context: PlasmicContext, pkg: string): string | undefined;
13
+ export declare function findInstalledVersion(context: PlasmicContext, baseDir: string, pkg: string): any;
14
14
  /**
15
15
  * Detects if the cli is globally installed. `rootDir` is the folder
16
16
  * where plasmic.json is
@@ -25,4 +25,4 @@ export declare function installCommand(pkg: string, baseDir: string, opts?: {
25
25
  global?: boolean;
26
26
  dev?: boolean;
27
27
  }): string;
28
- export declare function detectPackageManager(baseDir: string): "yarn" | "npm";
28
+ export declare function detectPackageManager(baseDir: string): "yarn2" | "yarn" | "npm";
@@ -60,7 +60,7 @@ exports.getParsedPackageJson = getParsedPackageJson;
60
60
  // @TODO: is this function still used?
61
61
  function warnLatest(context, pkg, baseDir, msgs, yes) {
62
62
  return __awaiter(this, void 0, void 0, function* () {
63
- const check = yield checkVersion(context, pkg);
63
+ const check = yield checkVersion(context, baseDir, pkg);
64
64
  if (check.type === "up-to-date") {
65
65
  return;
66
66
  }
@@ -76,7 +76,7 @@ function warnLatest(context, pkg, baseDir, msgs, yes) {
76
76
  });
77
77
  }
78
78
  exports.warnLatest = warnLatest;
79
- function checkVersion(context, pkg) {
79
+ function checkVersion(context, baseDir, pkg) {
80
80
  return __awaiter(this, void 0, void 0, function* () {
81
81
  // Try to get the latest version from npm
82
82
  let last = null;
@@ -87,7 +87,7 @@ function checkVersion(context, pkg) {
87
87
  // This is likely because .npmrc is set to a different registry
88
88
  return { type: "wrong-npm-registry" };
89
89
  }
90
- const cur = findInstalledVersion(context, pkg);
90
+ const cur = findInstalledVersion(context, baseDir, pkg);
91
91
  if (!cur) {
92
92
  return { type: "not-installed" };
93
93
  }
@@ -101,7 +101,18 @@ function checkVersion(context, pkg) {
101
101
  return { type: "up-to-date" };
102
102
  });
103
103
  }
104
- function findInstalledVersion(context, pkg) {
104
+ function findInstalledVersion(context, baseDir, pkg) {
105
+ var _a;
106
+ const pm = detectPackageManager(baseDir);
107
+ if (pm === "yarn2") {
108
+ try {
109
+ const pkgInfo = JSON.parse(child_process_1.execSync(`yarn info --json ${pkg}`).toString().trim());
110
+ return (_a = pkgInfo === null || pkgInfo === void 0 ? void 0 : pkgInfo.children) === null || _a === void 0 ? void 0 : _a.Version;
111
+ }
112
+ catch (_) {
113
+ return undefined;
114
+ }
115
+ }
105
116
  const filename = findInstalledPackageJsonFile(context, pkg);
106
117
  if (filename) {
107
118
  const json = parsePackageJson(filename);
@@ -190,6 +201,18 @@ function installCommand(pkg, baseDir, opts = {}) {
190
201
  return `yarn add --ignore-scripts -W ${pkg}`;
191
202
  }
192
203
  }
204
+ else if (mgr === "yarn2") {
205
+ if (opts.global) {
206
+ // yarn2 does not support global.
207
+ return `npm install -g ${pkg}`;
208
+ }
209
+ else if (opts.dev) {
210
+ return `yarn add -D ${pkg}`;
211
+ }
212
+ else {
213
+ return `yarn add ${pkg}`;
214
+ }
215
+ }
193
216
  else {
194
217
  if (opts.global) {
195
218
  return `npm install -g ${pkg}`;
@@ -206,10 +229,9 @@ exports.installCommand = installCommand;
206
229
  function detectPackageManager(baseDir) {
207
230
  const yarnLock = findup_sync_1.default("yarn.lock", { cwd: baseDir });
208
231
  if (yarnLock) {
209
- return "yarn";
210
- }
211
- else {
212
- return "npm";
232
+ const yarnVersion = child_process_1.execSync(`yarn --version`).toString().trim();
233
+ return semver_1.default.gte(yarnVersion, "2.0.0") ? "yarn2" : "yarn";
213
234
  }
235
+ return "npm";
214
236
  }
215
237
  exports.detectPackageManager = detectPackageManager;
@@ -47,6 +47,7 @@ const user_utils_1 = require("./user-utils");
47
47
  * @param versionResolution
48
48
  */
49
49
  function walkDependencyTree(root, available) {
50
+ root.indirect = false;
50
51
  const queue = [root];
51
52
  const result = [];
52
53
  const getMeta = (projectId, version) => {
@@ -54,7 +55,7 @@ function walkDependencyTree(root, available) {
54
55
  if (!meta) {
55
56
  throw new Error(`Cannot find projectId=${projectId}, version=${version} in the sync resolution results.`);
56
57
  }
57
- return meta;
58
+ return Object.assign(Object.assign({}, meta), { indirect: meta.projectId !== root.projectId });
58
59
  };
59
60
  while (queue.length > 0) {
60
61
  const curr = lang_utils_1.ensure(queue.shift());
@@ -73,6 +74,7 @@ function checkProjectMeta(meta, root, context, opts) {
73
74
  const projectId = meta.projectId;
74
75
  const projectName = meta.projectName;
75
76
  const newVersion = meta.version;
77
+ const indirect = meta.indirect;
76
78
  // Checks newVersion against plasmic.lock
77
79
  const checkVersionLock = () => __awaiter(this, void 0, void 0, function* () {
78
80
  const projectLock = context.lock.projects.find((p) => p.projectId === projectId);
@@ -145,6 +147,15 @@ function checkProjectMeta(meta, root, context, opts) {
145
147
  deps_1.logger.warn(`${projectName}@${newVersion} falls outside the range specified in ${config_utils_1.CONFIG_FILE_NAME} (${versionRange})\nTip: To avoid this warning in the future, update your ${config_utils_1.CONFIG_FILE_NAME}.`);
146
148
  return yield user_utils_1.confirmWithUser("Do you want to force it?", opts.force || opts.yes, "n");
147
149
  });
150
+ const checkIndirect = () => __awaiter(this, void 0, void 0, function* () {
151
+ const projectConfig = context.config.projects.find((p) => p.projectId === projectId);
152
+ const configIndirect = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.indirect;
153
+ if (configIndirect && !indirect) {
154
+ deps_1.logger.warn(`'${projectName}' was synced indirectly before, but a direct sync was requested. If it has page components, they will be synced and saved to plasmic.json.`);
155
+ return yield user_utils_1.confirmWithUser("Do you want to confirm it?", opts.force || opts.yes, "n");
156
+ }
157
+ return true;
158
+ });
148
159
  const projectIds = opts.projects.length > 0
149
160
  ? opts.projects
150
161
  : context.config.projects.map((p) => p.projectId);
@@ -153,7 +164,9 @@ function checkProjectMeta(meta, root, context, opts) {
153
164
  // we should always sync it, even if nothing has changed
154
165
  return true;
155
166
  }
156
- return (yield checkVersionLock()) && (yield checkVersionRange());
167
+ return ((yield checkVersionLock()) &&
168
+ (yield checkVersionRange()) &&
169
+ (yield checkIndirect()));
157
170
  });
158
171
  }
159
172
  /**
@@ -175,13 +188,18 @@ function checkVersionResolution(versionResolution, context, opts) {
175
188
  ? [root]
176
189
  : walkDependencyTree(root, versionResolution.dependencies).reverse();
177
190
  for (const m of queue) {
178
- // If we haven't seen this yet
179
191
  if (!seen.find((p) => p.projectId === m.projectId)) {
180
192
  if (yield checkProjectMeta(m, root, context, opts)) {
181
193
  result.push(m);
182
194
  }
183
195
  seen.push(m);
184
196
  }
197
+ else if (root.projectId === m.projectId) {
198
+ // If m is the root project and it was already seen (maybe as a dep
199
+ // for another project), set indirect = false.
200
+ const project = lang_utils_1.ensure(seen.find((p) => p.projectId === m.projectId));
201
+ project.indirect = false;
202
+ }
185
203
  }
186
204
  }
187
205
  // Ignore repeats
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasmicapp/cli",
3
- "version": "0.1.163",
3
+ "version": "0.1.167",
4
4
  "description": "plasmic cli for syncing local code with Plasmic designs",
5
5
  "engines": {
6
6
  "node": ">=12"
@@ -49,7 +49,9 @@
49
49
  "dependencies": {
50
50
  "@babel/core": "^7.12.3",
51
51
  "@babel/generator": "^7.12.1",
52
- "@plasmicapp/code-merger": "^0.0.31",
52
+ "@babel/parser": "^7.12.3",
53
+ "@babel/traverse": "^7.12.1",
54
+ "@plasmicapp/code-merger": "^0.0.33",
53
55
  "@sentry/node": "^5.19.2",
54
56
  "@types/socket.io-client": "^1.4.34",
55
57
  "axios": "^0.21.1",
@@ -61,6 +61,7 @@ function mockProjectToProjectVersionMeta(
61
61
  componentIdOrNames.includes(c.id)
62
62
  )
63
63
  .map((c) => c.id),
64
+ indirect: false,
64
65
  };
65
66
  }
66
67
 
@@ -339,6 +340,8 @@ class PlasmicApi {
339
340
  globalVariantChecksums: [],
340
341
  projectCssChecksum: "",
341
342
  } as ChecksumBundle,
343
+ usedNpmPackages: [],
344
+ externalCssImports: [],
342
345
  };
343
346
  return result;
344
347
  }
@@ -71,11 +71,13 @@ describe("Project API tokens", () => {
71
71
 
72
72
  expectProject1Components();
73
73
 
74
- expectProject1PlasmicJson();
74
+ expectProject1PlasmicJson({ projectApiToken: true });
75
75
 
76
76
  // Re-run, this time with no auth.
77
77
  removeAuth();
78
- await expect(sync(opts)).resolves.toBeUndefined();
78
+ await expect(sync(opts)).rejects.toThrow(
79
+ "No user+token, and project API tokens don't match"
80
+ );
79
81
  });
80
82
 
81
83
  test("is filled in by auth'd user if project exists but token was initially missing", async () => {
@@ -88,11 +90,13 @@ describe("Project API tokens", () => {
88
90
 
89
91
  expectProject1Components();
90
92
 
91
- expectProject1PlasmicJson();
93
+ expectProject1PlasmicJson({ projectApiToken: true });
92
94
 
93
95
  // Re-run, this time with no auth.
94
96
  removeAuth();
95
- await expect(sync(opts)).resolves.toBeUndefined();
97
+ await expect(sync(opts)).rejects.toThrow(
98
+ "Unable to authenticate Plasmic. Please run 'plasmic auth' or check the projectApiTokens in your plasmic.json, and try again."
99
+ );
96
100
  });
97
101
 
98
102
  test("when not available, should prompt for auth", async () => {
@@ -128,6 +128,7 @@ async function ensureRequiredPackages(
128
128
 
129
129
  const reactWebVersion = findInstalledVersion(
130
130
  context,
131
+ baseDir,
131
132
  "@plasmicapp/react-web"
132
133
  );
133
134
  if (
@@ -145,6 +146,7 @@ async function ensureRequiredPackages(
145
146
  // Using automatic runtime requires installing the @plasmicapp/react-web-runtime package
146
147
  const runtimeVersion = findInstalledVersion(
147
148
  context,
149
+ baseDir,
148
150
  "@plasmicapp/react-web-runtime"
149
151
  );
150
152
  if (
@@ -232,6 +234,7 @@ export async function sync(
232
234
  versionRange || projectConfigMap[projectId]?.version || "latest",
233
235
  componentIdOrNames: undefined, // Get all components!
234
236
  projectApiToken: projectApiToken || projectIdToToken.get(projectId),
237
+ indirect: false,
235
238
  };
236
239
  });
237
240
 
@@ -242,6 +245,7 @@ export async function sync(
242
245
  versionRange: p.version,
243
246
  componentIdOrNames: undefined, // Get all components!
244
247
  projectApiToken: p.projectApiToken,
248
+ indirect: !!p.indirect,
245
249
  }));
246
250
 
247
251
  // Short-circuit if nothing to sync
@@ -257,7 +261,7 @@ export async function sync(
257
261
  try {
258
262
  context = await getContext(opts);
259
263
  } catch (e) {
260
- if (e.message.includes("Unable to authenticate Plasmic")) {
264
+ if ((e as any).message.includes("Unable to authenticate Plasmic")) {
261
265
  const configFileName = process.env.PLASMIC_LOADER
262
266
  ? LOADER_CONFIG_FILE_NAME
263
267
  : CONFIG_FILE_NAME;
@@ -311,6 +315,8 @@ export async function sync(
311
315
  ].map((p) => L.pick(p, "projectId", "projectApiToken"));
312
316
 
313
317
  context.api.attachProjectIdsAndTokens(projectIdsAndTokens);
318
+ const externalNpmPackages = new Set<string>();
319
+ const externalCssImports = new Set<string>();
314
320
 
315
321
  // Perform the actual sync
316
322
  await withBufferedFs(async () => {
@@ -327,6 +333,9 @@ export async function sync(
327
333
  projectMeta.dependencies,
328
334
  summary,
329
335
  pendingMerge,
336
+ projectMeta.indirect,
337
+ externalNpmPackages,
338
+ externalCssImports,
330
339
  metadataDefaults
331
340
  );
332
341
  }
@@ -345,15 +354,22 @@ export async function sync(
345
354
  await context.api.genStyleConfig(context.config.style)
346
355
  );
347
356
 
348
- // Update project version if specified and successfully synced.
357
+ // Update project version and indirect status if specified and
358
+ // successfully synced.
349
359
  if (projectWithVersion.length) {
350
360
  const versionMap: Record<string, string> = {};
351
361
  projectWithVersion.forEach(
352
362
  (p) => (versionMap[p.projectId] = p.versionRange)
353
363
  );
354
- context.config.projects.forEach(
355
- (p) => (p.version = versionMap[p.projectId] || p.version)
356
- );
364
+ const indirectMap: Record<string, boolean> = {};
365
+ projectsToSync.forEach((p) => (indirectMap[p.projectId] = p.indirect));
366
+ context.config.projects.forEach((p) => {
367
+ p.version = versionMap[p.projectId] || p.version;
368
+ // Only update `indirect` if it is set in current config.
369
+ if (p.projectId in indirectMap && p.indirect) {
370
+ p.indirect = indirectMap[p.projectId];
371
+ }
372
+ });
357
373
  }
358
374
 
359
375
  // Fix imports
@@ -404,6 +420,22 @@ export async function sync(
404
420
  await updateConfig(context, context.config, baseDir);
405
421
  });
406
422
 
423
+ await checkExternalPkgs(
424
+ context,
425
+ baseDir,
426
+ opts,
427
+ Array.from(externalNpmPackages.keys())
428
+ );
429
+
430
+ if (!opts.quiet && externalCssImports.size > 0) {
431
+ logger.info(
432
+ `This project uses external packages and styles. Make sure to import the following global CSS: ` +
433
+ Array.from(externalCssImports.keys())
434
+ .map((stmt) => `"${stmt}"`)
435
+ .join(", ")
436
+ );
437
+ }
438
+
407
439
  // Post-sync commands
408
440
  if (!opts.ignorePostSync) {
409
441
  for (const cmd of context.config.postSyncCommands || []) {
@@ -418,6 +450,30 @@ export async function sync(
418
450
  }
419
451
  }
420
452
 
453
+ async function checkExternalPkgs(
454
+ context: PlasmicContext,
455
+ baseDir: string,
456
+ opts: SyncArgs,
457
+ pkgs: string[]
458
+ ) {
459
+ const missingPkgs = pkgs.filter((pkg) => {
460
+ const installedPkg = findInstalledVersion(context, baseDir, pkg);
461
+ return !installedPkg;
462
+ });
463
+ if (missingPkgs.length > 0) {
464
+ const upgrade = await confirmWithUser(
465
+ `The following packages aren't installed but are required by some projects, would you like to install them? ${missingPkgs.join(
466
+ ", "
467
+ )}`,
468
+ opts.yes
469
+ );
470
+
471
+ if (upgrade) {
472
+ installUpgrade(missingPkgs.join(" "), baseDir);
473
+ }
474
+ }
475
+ }
476
+
421
477
  function maybeRenamePathExt(
422
478
  context: PlasmicContext,
423
479
  path: string,
@@ -467,6 +523,9 @@ async function syncProject(
467
523
  dependencies: { [projectId: string]: string },
468
524
  summary: Map<string, ComponentUpdateSummary>,
469
525
  pendingMerge: ComponentPendingMerge[],
526
+ indirect: boolean,
527
+ externalNpmPackages: Set<string>,
528
+ externalCssImports: Set<string>,
470
529
  metadataDefaults?: Metadata
471
530
  ): Promise<void> {
472
531
  const newComponentScheme =
@@ -510,6 +569,7 @@ async function syncProject(
510
569
  },
511
570
  opts.metadata
512
571
  ),
572
+ indirect,
513
573
  });
514
574
 
515
575
  // Convert from TSX => JSX
@@ -556,8 +616,6 @@ async function syncProject(
556
616
  opts.baseDir
557
617
  );
558
618
 
559
- syncCodeComponentsMeta(context, projectId, projectBundle.codeComponentMetas);
560
-
561
619
  await syncProjectConfig(
562
620
  context,
563
621
  projectBundle.projectConfig,
@@ -570,8 +628,10 @@ async function syncProject(
570
628
  summary,
571
629
  pendingMerge,
572
630
  projectBundle.checksums,
573
- opts.baseDir
631
+ opts.baseDir,
632
+ indirect
574
633
  );
634
+ syncCodeComponentsMeta(context, projectId, projectBundle.codeComponentMetas);
575
635
  await upsertStyleTokens(context, projectBundle.usedTokens);
576
636
  await syncProjectIconAssets(
577
637
  context,
@@ -588,6 +648,12 @@ async function syncProject(
588
648
  projectBundle.imageAssets,
589
649
  projectBundle.checksums
590
650
  );
651
+ (projectBundle.usedNpmPackages || []).forEach((pkg) =>
652
+ externalNpmPackages.add(pkg)
653
+ );
654
+ (projectBundle.externalCssImports || []).forEach((css) =>
655
+ externalCssImports.add(css)
656
+ );
591
657
  }
592
658
 
593
659
  async function syncStyleConfig(
@@ -618,7 +684,8 @@ async function syncProjectConfig(
618
684
  summary: Map<string, ComponentUpdateSummary>,
619
685
  pendingMerge: ComponentPendingMerge[],
620
686
  checksums: ChecksumBundle,
621
- baseDir: string
687
+ baseDir: string,
688
+ indirect: boolean
622
689
  ) {
623
690
  const defaultCssFilePath = defaultResourcePath(
624
691
  context,
@@ -629,8 +696,6 @@ async function syncProjectConfig(
629
696
  (p) => p.projectId === projectBundle.projectId
630
697
  );
631
698
 
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
699
  const projectConfig = getOrAddProjectConfig(
635
700
  context,
636
701
  projectBundle.projectId,
@@ -638,8 +703,9 @@ async function syncProjectConfig(
638
703
  projectId: projectBundle.projectId,
639
704
  projectApiToken,
640
705
  projectName: projectBundle.projectName,
641
- version: versionRange,
706
+ version,
642
707
  cssFilePath: defaultCssFilePath,
708
+ indirect,
643
709
  })
644
710
  );
645
711
 
@@ -648,7 +714,6 @@ async function syncProjectConfig(
648
714
  if (!projectConfig.cssFilePath) {
649
715
  projectConfig.cssFilePath = defaultCssFilePath;
650
716
  }
651
- projectConfig.projectApiToken = projectApiToken;
652
717
 
653
718
  // plasmic.lock
654
719
  const projectLock = getOrAddProjectLock(context, projectConfig.projectId);
package/src/api.ts CHANGED
@@ -76,6 +76,7 @@ export interface ProjectVersionMeta {
76
76
  dependencies: {
77
77
  [projectId: string]: string;
78
78
  };
79
+ indirect: boolean;
79
80
  }
80
81
 
81
82
  export interface VersionResolution {
@@ -100,6 +101,8 @@ export interface ProjectBundle {
100
101
  iconAssets: IconBundle[];
101
102
  imageAssets: ImageBundle[];
102
103
  checksums: ChecksumBundle;
104
+ usedNpmPackages: string[];
105
+ externalCssImports: string[];
103
106
  }
104
107
 
105
108
  export type ProjectMeta = Omit<ProjectBundle, "projectConfig">;
@@ -235,6 +238,7 @@ export class PlasmicApi {
235
238
  stylesOpts: StyleConfig;
236
239
  codeOpts: CodeConfig;
237
240
  checksums: ChecksumBundle;
241
+ indirect: boolean;
238
242
  metadata?: Metadata;
239
243
  }
240
244
  ): Promise<ProjectBundle> {
@@ -0,0 +1,10 @@
1
+ import { PlasmicConfig } from "../utils/config-utils";
2
+
3
+ export function ensureIndirect(config: PlasmicConfig) {
4
+ for (const p of config.projects) {
5
+ if (p.indirect === undefined) {
6
+ p.indirect = false;
7
+ }
8
+ }
9
+ return config;
10
+ }
@@ -35,6 +35,7 @@ import {
35
35
  import { confirmWithUser } from "../utils/user-utils";
36
36
  import { ensureFileLocks } from "./0.1.110-fileLocks";
37
37
  import { ensureReactRuntime } from "./0.1.146-addReactRuntime";
38
+ import { ensureIndirect } from "./0.1.166-indirect";
38
39
  import { migrateInit } from "./0.1.27-migrateInit";
39
40
  import { tsToTsx } from "./0.1.28-tsToTsx";
40
41
  import { ensureProjectIcons } from "./0.1.31-ensureProjectIcons";
@@ -64,6 +65,7 @@ export const MIGRATIONS: Record<string, MigrateConfigFunc> = {
64
65
  "0.1.64": ensureImageFiles,
65
66
  "0.1.95": ensureComponentType,
66
67
  "0.1.146": ensureReactRuntime,
68
+ "0.1.166": ensureIndirect,
67
69
  };
68
70
 
69
71
  export const LOCK_MIGRATIONS: Record<string, MigrateLockFunc> = {
@@ -147,13 +147,18 @@ export const project1Config: ProjectConfig = {
147
147
  icons: [],
148
148
  images: [],
149
149
  jsBundleThemes: [],
150
+ indirect: false,
150
151
  };
151
152
 
152
- export function expectProject1PlasmicJson() {
153
+ export function expectProject1PlasmicJson(
154
+ optional?: { [k in keyof ProjectConfig]?: boolean }
155
+ ) {
153
156
  const plasmicJson = tmpRepo.readPlasmicJson();
154
157
  expect(plasmicJson.projects.length).toEqual(1);
155
158
  const projectConfig = plasmicJson.projects[0];
156
- expect(projectConfig.projectApiToken).toBe("abc");
159
+ if (!optional?.projectApiToken) {
160
+ expect(projectConfig.projectApiToken).toBe("abc");
161
+ }
157
162
  expect(projectConfig.components.length).toEqual(2);
158
163
  const componentNames = projectConfig.components.map((c) => c.name);
159
164
  expect(componentNames).toContain("Button");
@@ -171,6 +171,12 @@ export interface ProjectConfig {
171
171
  icons: IconConfig[];
172
172
  /** Metadata for each synced image in this project */
173
173
  images: ImageConfig[];
174
+
175
+ /**
176
+ * True if the project was installed indirectly (as a dependency); if set,
177
+ * codegen will not generate pages.
178
+ */
179
+ indirect: boolean;
174
180
  }
175
181
 
176
182
  export function createProjectConfig(base: {
@@ -179,6 +185,7 @@ export function createProjectConfig(base: {
179
185
  projectName: string;
180
186
  version: string;
181
187
  cssFilePath: string;
188
+ indirect: boolean;
182
189
  }): ProjectConfig {
183
190
  return {
184
191
  projectId: base.projectId,
@@ -189,6 +196,7 @@ export function createProjectConfig(base: {
189
196
  components: [],
190
197
  icons: [],
191
198
  images: [],
199
+ indirect: base.indirect,
192
200
  };
193
201
  }
194
202
 
@@ -519,6 +527,7 @@ export function getOrAddProjectConfig(
519
527
  icons: [],
520
528
  images: [],
521
529
  jsBundleThemes: [],
530
+ indirect: false,
522
531
  };
523
532
  context.config.projects.push(project);
524
533
  }
@@ -1,4 +1,4 @@
1
- import { spawnSync } from "child_process";
1
+ import { execSync, spawnSync } from "child_process";
2
2
  import glob from "fast-glob";
3
3
  import findupSync from "findup-sync";
4
4
  import latest from "latest-version";
@@ -54,7 +54,7 @@ export async function warnLatest(
54
54
  },
55
55
  yes?: boolean
56
56
  ) {
57
- const check = await checkVersion(context, pkg);
57
+ const check = await checkVersion(context, baseDir, pkg);
58
58
  if (check.type === "up-to-date") {
59
59
  return;
60
60
  } else if (check.type === "wrong-npm-registry") {
@@ -80,7 +80,11 @@ export async function warnLatest(
80
80
  }
81
81
  }
82
82
 
83
- async function checkVersion(context: PlasmicContext, pkg: string) {
83
+ async function checkVersion(
84
+ context: PlasmicContext,
85
+ baseDir: string,
86
+ pkg: string
87
+ ) {
84
88
  // Try to get the latest version from npm
85
89
  let last = null;
86
90
  try {
@@ -90,7 +94,7 @@ async function checkVersion(context: PlasmicContext, pkg: string) {
90
94
  return { type: "wrong-npm-registry" } as const;
91
95
  }
92
96
 
93
- const cur = findInstalledVersion(context, pkg);
97
+ const cur = findInstalledVersion(context, baseDir, pkg);
94
98
  if (!cur) {
95
99
  return { type: "not-installed" } as const;
96
100
  }
@@ -104,7 +108,23 @@ async function checkVersion(context: PlasmicContext, pkg: string) {
104
108
  return { type: "up-to-date" } as const;
105
109
  }
106
110
 
107
- export function findInstalledVersion(context: PlasmicContext, pkg: string) {
111
+ export function findInstalledVersion(
112
+ context: PlasmicContext,
113
+ baseDir: string,
114
+ pkg: string
115
+ ) {
116
+ const pm = detectPackageManager(baseDir);
117
+ if (pm === "yarn2") {
118
+ try {
119
+ const pkgInfo = JSON.parse(
120
+ execSync(`yarn info --json ${pkg}`).toString().trim()
121
+ );
122
+ return pkgInfo?.children?.Version;
123
+ } catch (_) {
124
+ return undefined;
125
+ }
126
+ }
127
+
108
128
  const filename = findInstalledPackageJsonFile(context, pkg);
109
129
  if (filename) {
110
130
  const json = parsePackageJson(filename);
@@ -202,6 +222,15 @@ export function installCommand(
202
222
  } else {
203
223
  return `yarn add --ignore-scripts -W ${pkg}`;
204
224
  }
225
+ } else if (mgr === "yarn2") {
226
+ if (opts.global) {
227
+ // yarn2 does not support global.
228
+ return `npm install -g ${pkg}`;
229
+ } else if (opts.dev) {
230
+ return `yarn add -D ${pkg}`;
231
+ } else {
232
+ return `yarn add ${pkg}`;
233
+ }
205
234
  } else {
206
235
  if (opts.global) {
207
236
  return `npm install -g ${pkg}`;
@@ -216,8 +245,9 @@ export function installCommand(
216
245
  export function detectPackageManager(baseDir: string) {
217
246
  const yarnLock = findupSync("yarn.lock", { cwd: baseDir });
218
247
  if (yarnLock) {
219
- return "yarn";
220
- } else {
221
- return "npm";
248
+ const yarnVersion = execSync(`yarn --version`).toString().trim();
249
+ return semver.gte(yarnVersion, "2.0.0") ? "yarn2" : "yarn";
222
250
  }
251
+
252
+ return "npm";
223
253
  }
@@ -1,4 +1,5 @@
1
1
  import L from "lodash";
2
+ import { InvalidatedProjectKind } from "typescript";
2
3
  import { SyncArgs } from "../actions/sync";
3
4
  import { ProjectVersionMeta, VersionResolution } from "../api";
4
5
  import { logger } from "../deps";
@@ -19,6 +20,7 @@ function walkDependencyTree(
19
20
  root: ProjectVersionMeta,
20
21
  available: ProjectVersionMeta[]
21
22
  ): ProjectVersionMeta[] {
23
+ root.indirect = false;
22
24
  const queue: ProjectVersionMeta[] = [root];
23
25
  const result: ProjectVersionMeta[] = [];
24
26
 
@@ -31,7 +33,10 @@ function walkDependencyTree(
31
33
  `Cannot find projectId=${projectId}, version=${version} in the sync resolution results.`
32
34
  );
33
35
  }
34
- return meta;
36
+ return {
37
+ ...meta,
38
+ indirect: meta.projectId !== root.projectId,
39
+ };
35
40
  };
36
41
 
37
42
  while (queue.length > 0) {
@@ -61,6 +66,7 @@ async function checkProjectMeta(
61
66
  const projectId = meta.projectId;
62
67
  const projectName = meta.projectName;
63
68
  const newVersion = meta.version;
69
+ const indirect = meta.indirect;
64
70
 
65
71
  // Checks newVersion against plasmic.lock
66
72
  const checkVersionLock = async (): Promise<boolean> => {
@@ -177,6 +183,24 @@ async function checkProjectMeta(
177
183
  );
178
184
  };
179
185
 
186
+ const checkIndirect = async (): Promise<boolean> => {
187
+ const projectConfig = context.config.projects.find(
188
+ (p) => p.projectId === projectId
189
+ );
190
+ const configIndirect = projectConfig?.indirect;
191
+ if (configIndirect && !indirect) {
192
+ logger.warn(
193
+ `'${projectName}' was synced indirectly before, but a direct sync was requested. If it has page components, they will be synced and saved to plasmic.json.`
194
+ );
195
+ return await confirmWithUser(
196
+ "Do you want to confirm it?",
197
+ opts.force || opts.yes,
198
+ "n"
199
+ );
200
+ }
201
+ return true;
202
+ };
203
+
180
204
  const projectIds =
181
205
  opts.projects.length > 0
182
206
  ? opts.projects
@@ -188,7 +212,11 @@ async function checkProjectMeta(
188
212
  return true;
189
213
  }
190
214
 
191
- return (await checkVersionLock()) && (await checkVersionRange());
215
+ return (
216
+ (await checkVersionLock()) &&
217
+ (await checkVersionRange()) &&
218
+ (await checkIndirect())
219
+ );
192
220
  }
193
221
 
194
222
  /**
@@ -216,12 +244,16 @@ export async function checkVersionResolution(
216
244
  ? [root]
217
245
  : walkDependencyTree(root, versionResolution.dependencies).reverse();
218
246
  for (const m of queue) {
219
- // If we haven't seen this yet
220
247
  if (!seen.find((p) => p.projectId === m.projectId)) {
221
248
  if (await checkProjectMeta(m, root, context, opts)) {
222
249
  result.push(m);
223
250
  }
224
251
  seen.push(m);
252
+ } else if (root.projectId === m.projectId) {
253
+ // If m is the root project and it was already seen (maybe as a dep
254
+ // for another project), set indirect = false.
255
+ const project = ensure(seen.find((p) => p.projectId === m.projectId));
256
+ project.indirect = false;
225
257
  }
226
258
  }
227
259
  }