@plasmicapp/cli 0.1.164 → 0.1.168

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
  });
@@ -274,6 +276,12 @@ class PlasmicApi {
274
276
  throw new Error("Unimplemented");
275
277
  });
276
278
  }
279
+ latestCodegenVersion() {
280
+ return __awaiter(this, void 0, void 0, function* () {
281
+ return "0.0.1";
282
+ });
283
+ }
284
+ ;
277
285
  requiredPackages() {
278
286
  return __awaiter(this, void 0, void 0, function* () {
279
287
  return {
@@ -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);
@@ -244,9 +257,22 @@ function sync(opts, metadataDefaults) {
244
257
  const config = Object.assign(Object.assign({}, loaderConfig), { projects: lodash_1.default.sortBy(lodash_1.default.uniqBy([...freshIdsAndTokens, ...((_b = loaderConfig === null || loaderConfig === void 0 ? void 0 : loaderConfig.projects) !== null && _b !== void 0 ? _b : [])], (p) => p.projectId), (p) => p.projectId) });
245
258
  writeLoaderConfig(opts, config);
246
259
  }
260
+ const codegenVersion = yield context.api.latestCodegenVersion();
261
+ context.lock.projects.forEach(p => {
262
+ if (projectsToSync.some(syncedProject => syncedProject.projectId === p.projectId)) {
263
+ p.codegenVersion = codegenVersion;
264
+ }
265
+ });
247
266
  // Write the new ComponentConfigs to disk
248
267
  yield config_utils_1.updateConfig(context, context.config, baseDir);
249
268
  }));
269
+ yield checkExternalPkgs(context, baseDir, opts, Array.from(externalNpmPackages.keys()));
270
+ if (!opts.quiet && externalCssImports.size > 0) {
271
+ deps_1.logger.info(`This project uses external packages and styles. Make sure to import the following global CSS: ` +
272
+ Array.from(externalCssImports.keys())
273
+ .map((stmt) => `"${stmt}"`)
274
+ .join(", "));
275
+ }
250
276
  // Post-sync commands
251
277
  if (!opts.ignorePostSync) {
252
278
  for (const cmd of context.config.postSyncCommands || []) {
@@ -261,6 +287,20 @@ function sync(opts, metadataDefaults) {
261
287
  });
262
288
  }
263
289
  exports.sync = sync;
290
+ function checkExternalPkgs(context, baseDir, opts, pkgs) {
291
+ return __awaiter(this, void 0, void 0, function* () {
292
+ const missingPkgs = pkgs.filter((pkg) => {
293
+ const installedPkg = npm_utils_1.findInstalledVersion(context, baseDir, pkg);
294
+ return !installedPkg;
295
+ });
296
+ if (missingPkgs.length > 0) {
297
+ 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);
298
+ if (upgrade) {
299
+ npm_utils_1.installUpgrade(missingPkgs.join(" "), baseDir);
300
+ }
301
+ }
302
+ });
303
+ }
264
304
  function maybeRenamePathExt(context, path, ext) {
265
305
  if (!path) {
266
306
  return path;
@@ -281,7 +321,7 @@ function fixFileExtension(context) {
281
321
  });
282
322
  });
283
323
  }
284
- function syncProject(context, opts, projectIdsAndTokens, projectId, componentIds, projectVersion, dependencies, summary, pendingMerge, metadataDefaults) {
324
+ function syncProject(context, opts, projectIdsAndTokens, projectId, componentIds, projectVersion, dependencies, summary, pendingMerge, indirect, externalNpmPackages, externalCssImports, metadataDefaults) {
285
325
  var _a;
286
326
  return __awaiter(this, void 0, void 0, function* () {
287
327
  const newComponentScheme = opts.newComponentScheme || context.config.code.scheme;
@@ -301,6 +341,7 @@ function syncProject(context, opts, projectIdsAndTokens, projectId, componentIds
301
341
  checksums: existingChecksums,
302
342
  codeOpts: context.config.code,
303
343
  metadata: get_context_1.generateMetadata(Object.assign(Object.assign({}, metadataDefaults), { platform: context.config.platform }), opts.metadata),
344
+ indirect,
304
345
  });
305
346
  // Convert from TSX => JSX
306
347
  if (context.config.code.lang === "js") {
@@ -319,11 +360,13 @@ function syncProject(context, opts, projectIdsAndTokens, projectId, componentIds
319
360
  });
320
361
  }
321
362
  yield sync_global_variants_1.syncGlobalVariants(context, projectBundle.projectConfig, projectBundle.globalVariants, projectBundle.checksums, opts.baseDir);
322
- yield syncProjectConfig(context, projectBundle.projectConfig, projectApiToken, projectVersion, dependencies, projectBundle.components, opts.forceOverwrite, !!opts.appendJsxOnMissingBase, summary, pendingMerge, projectBundle.checksums, opts.baseDir);
363
+ yield syncProjectConfig(context, projectBundle.projectConfig, projectApiToken, projectVersion, dependencies, projectBundle.components, opts.forceOverwrite, !!opts.appendJsxOnMissingBase, summary, pendingMerge, projectBundle.checksums, opts.baseDir, indirect);
323
364
  syncCodeComponentsMeta(context, projectId, projectBundle.codeComponentMetas);
324
365
  yield sync_styles_1.upsertStyleTokens(context, projectBundle.usedTokens);
325
366
  yield sync_icons_1.syncProjectIconAssets(context, projectId, projectVersion, projectBundle.iconAssets, projectBundle.checksums, opts.baseDir);
326
367
  yield sync_images_1.syncProjectImageAssets(context, projectId, projectVersion, projectBundle.imageAssets, projectBundle.checksums);
368
+ (projectBundle.usedNpmPackages || []).forEach((pkg) => externalNpmPackages.add(pkg));
369
+ (projectBundle.externalCssImports || []).forEach((css) => externalCssImports.add(css));
327
370
  });
328
371
  }
329
372
  function syncStyleConfig(context, response) {
@@ -336,7 +379,7 @@ function syncStyleConfig(context, response) {
336
379
  });
337
380
  });
338
381
  }
339
- function syncProjectConfig(context, projectBundle, projectApiToken, version, dependencies, componentBundles, forceOverwrite, appendJsxOnMissingBase, summary, pendingMerge, checksums, baseDir) {
382
+ function syncProjectConfig(context, projectBundle, projectApiToken, version, dependencies, componentBundles, forceOverwrite, appendJsxOnMissingBase, summary, pendingMerge, checksums, baseDir, indirect) {
340
383
  return __awaiter(this, void 0, void 0, function* () {
341
384
  const defaultCssFilePath = file_utils_1.defaultResourcePath(context, projectBundle.projectName, projectBundle.cssFileName);
342
385
  const isNew = !context.config.projects.find((p) => p.projectId === projectBundle.projectId);
@@ -346,13 +389,13 @@ function syncProjectConfig(context, projectBundle, projectApiToken, version, dep
346
389
  projectName: projectBundle.projectName,
347
390
  version,
348
391
  cssFilePath: defaultCssFilePath,
392
+ indirect,
349
393
  }));
350
394
  // Update missing/outdated props
351
395
  projectConfig.projectName = projectBundle.projectName;
352
396
  if (!projectConfig.cssFilePath) {
353
397
  projectConfig.cssFilePath = defaultCssFilePath;
354
398
  }
355
- projectConfig.projectApiToken = projectApiToken;
356
399
  // plasmic.lock
357
400
  const projectLock = config_utils_1.getOrAddProjectLock(context, projectConfig.projectId);
358
401
  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 {
@@ -124,6 +127,7 @@ export interface ProjectIdAndToken {
124
127
  }
125
128
  export declare class PlasmicApi {
126
129
  private auth;
130
+ private codegenVersion?;
127
131
  constructor(auth: AuthConfig);
128
132
  genStyleConfig(styleOpts?: StyleConfig): Promise<StyleConfigResponse>;
129
133
  /**
@@ -143,6 +147,7 @@ export declare class PlasmicApi {
143
147
  }[], recursive?: boolean): Promise<VersionResolution>;
144
148
  getCurrentUser(): Promise<import("axios").AxiosResponse<any>>;
145
149
  requiredPackages(): Promise<RequiredPackages>;
150
+ latestCodegenVersion(): Promise<string>;
146
151
  /**
147
152
  * Code-gen endpoint.
148
153
  * This will fetch components at an exact specified version.
@@ -165,6 +170,7 @@ export declare class PlasmicApi {
165
170
  stylesOpts: StyleConfig;
166
171
  codeOpts: CodeConfig;
167
172
  checksums: ChecksumBundle;
173
+ indirect: boolean;
168
174
  metadata?: Metadata;
169
175
  }): Promise<ProjectBundle>;
170
176
  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>;
package/dist/api.js CHANGED
@@ -66,6 +66,15 @@ class PlasmicApi {
66
66
  return Object.assign({}, resp.data);
67
67
  });
68
68
  }
69
+ latestCodegenVersion() {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ if (!this.codegenVersion) {
72
+ const resp = yield this.post(`${this.codegenHost}/api/v1/code/latest-codegen-version`);
73
+ this.codegenVersion = resp.data;
74
+ }
75
+ return this.codegenVersion;
76
+ });
77
+ }
69
78
  /**
70
79
  * Code-gen endpoint.
71
80
  * This will fetch components at an exact specified version.
@@ -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";
@@ -219,6 +225,7 @@ export interface ProjectLock {
219
225
  };
220
226
  lang: "ts" | "js";
221
227
  fileLocks: FileLock[];
228
+ codegenVersion?: string;
222
229
  }
223
230
  export interface PlasmicLock {
224
231
  projects: ProjectLock[];
@@ -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,16 @@ 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;
78
+ // If the codegen version on-disk is invalid, we will sync again the project.
79
+ const checkCodegenVersion = () => __awaiter(this, void 0, void 0, function* () {
80
+ const projectLock = context.lock.projects.find((p) => p.projectId === projectId);
81
+ if (!!(projectLock === null || projectLock === void 0 ? void 0 : projectLock.codegenVersion) && semver.gte(projectLock.codegenVersion, yield context.api.latestCodegenVersion())) {
82
+ return false;
83
+ }
84
+ return true;
85
+ });
86
+ const isOnDiskCodeInvalid = yield checkCodegenVersion();
76
87
  // Checks newVersion against plasmic.lock
77
88
  const checkVersionLock = () => __awaiter(this, void 0, void 0, function* () {
78
89
  const projectLock = context.lock.projects.find((p) => p.projectId === projectId);
@@ -86,7 +97,9 @@ function checkProjectMeta(meta, root, context, opts) {
86
97
  meta !== root) {
87
98
  // If this is a dependency (not root), and we're dealing with latest dep version
88
99
  // just skip, it's confusing
89
- deps_1.logger.warn(`'${root.projectName}' depends on ${projectName}@${newVersion}. To update this project, explicitly specify this project for sync. Skipping...`);
100
+ if (!isOnDiskCodeInvalid) {
101
+ deps_1.logger.warn(`'${root.projectName}' depends on ${projectName}@${newVersion}. To update this project, explicitly specify this project for sync. Skipping...`);
102
+ }
90
103
  return false;
91
104
  }
92
105
  if (semver.isLatest(newVersion)) {
@@ -104,7 +117,9 @@ function checkProjectMeta(meta, root, context, opts) {
104
117
  return true;
105
118
  }
106
119
  else {
107
- deps_1.logger.info(`Project '${projectName}'@${newVersion} is already up to date; skipping. (To force an update, run again with "--force")`);
120
+ if (!isOnDiskCodeInvalid) {
121
+ deps_1.logger.info(`Project '${projectName}'@${newVersion} is already up to date; skipping. (To force an update, run again with "--force")`);
122
+ }
108
123
  return false;
109
124
  }
110
125
  }
@@ -145,6 +160,15 @@ function checkProjectMeta(meta, root, context, opts) {
145
160
  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
161
  return yield user_utils_1.confirmWithUser("Do you want to force it?", opts.force || opts.yes, "n");
147
162
  });
163
+ const checkIndirect = () => __awaiter(this, void 0, void 0, function* () {
164
+ const projectConfig = context.config.projects.find((p) => p.projectId === projectId);
165
+ const configIndirect = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.indirect;
166
+ if (configIndirect && !indirect) {
167
+ 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.`);
168
+ return yield user_utils_1.confirmWithUser("Do you want to confirm it?", opts.force || opts.yes, "n");
169
+ }
170
+ return true;
171
+ });
148
172
  const projectIds = opts.projects.length > 0
149
173
  ? opts.projects
150
174
  : context.config.projects.map((p) => p.projectId);
@@ -153,7 +177,24 @@ function checkProjectMeta(meta, root, context, opts) {
153
177
  // we should always sync it, even if nothing has changed
154
178
  return true;
155
179
  }
156
- return (yield checkVersionLock()) && (yield checkVersionRange());
180
+ const checkedVersion = (yield checkVersionLock()) &&
181
+ (yield checkVersionRange()) &&
182
+ (yield checkIndirect());
183
+ if (!checkedVersion && isOnDiskCodeInvalid) {
184
+ // sync, but try to keep the current version on disk
185
+ const projectLock = context.lock.projects.find((p) => p.projectId === projectId);
186
+ const versionOnDisk = projectLock === null || projectLock === void 0 ? void 0 : projectLock.version;
187
+ deps_1.logger.warn(`Project '${projectName}' was synced by an incompatible version of Plasmic Codegen. Syncing again on the same version ${projectName}@${versionOnDisk}`);
188
+ meta.version = versionOnDisk !== null && versionOnDisk !== void 0 ? versionOnDisk : meta.version;
189
+ return true;
190
+ }
191
+ else if (checkedVersion) {
192
+ // sync and upgrade the version
193
+ return true;
194
+ }
195
+ else {
196
+ return false;
197
+ }
157
198
  });
158
199
  }
159
200
  /**
@@ -175,13 +216,18 @@ function checkVersionResolution(versionResolution, context, opts) {
175
216
  ? [root]
176
217
  : walkDependencyTree(root, versionResolution.dependencies).reverse();
177
218
  for (const m of queue) {
178
- // If we haven't seen this yet
179
219
  if (!seen.find((p) => p.projectId === m.projectId)) {
180
220
  if (yield checkProjectMeta(m, root, context, opts)) {
181
221
  result.push(m);
182
222
  }
183
223
  seen.push(m);
184
224
  }
225
+ else if (root.projectId === m.projectId) {
226
+ // If m is the root project and it was already seen (maybe as a dep
227
+ // for another project), set indirect = false.
228
+ const project = lang_utils_1.ensure(seen.find((p) => p.projectId === m.projectId));
229
+ project.indirect = false;
230
+ }
185
231
  }
186
232
  }
187
233
  // Ignore repeats
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasmicapp/cli",
3
- "version": "0.1.164",
3
+ "version": "0.1.168",
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
  }
@@ -369,6 +372,10 @@ class PlasmicApi {
369
372
  throw new Error("Unimplemented");
370
373
  }
371
374
 
375
+ async latestCodegenVersion(): Promise<string> {
376
+ return "0.0.1";
377
+ };
378
+
372
379
  async requiredPackages(): Promise<RequiredPackages> {
373
380
  return {
374
381
  "@plasmicapp/loader": "0.0.1",
@@ -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
@@ -400,10 +416,32 @@ export async function sync(
400
416
  writeLoaderConfig(opts, config);
401
417
  }
402
418
 
419
+ const codegenVersion = await context.api.latestCodegenVersion();
420
+ context.lock.projects.forEach(p => {
421
+ if (projectsToSync.some(syncedProject => syncedProject.projectId === p.projectId)) {
422
+ p.codegenVersion = codegenVersion;
423
+ }
424
+ })
403
425
  // Write the new ComponentConfigs to disk
404
426
  await updateConfig(context, context.config, baseDir);
405
427
  });
406
428
 
429
+ await checkExternalPkgs(
430
+ context,
431
+ baseDir,
432
+ opts,
433
+ Array.from(externalNpmPackages.keys())
434
+ );
435
+
436
+ if (!opts.quiet && externalCssImports.size > 0) {
437
+ logger.info(
438
+ `This project uses external packages and styles. Make sure to import the following global CSS: ` +
439
+ Array.from(externalCssImports.keys())
440
+ .map((stmt) => `"${stmt}"`)
441
+ .join(", ")
442
+ );
443
+ }
444
+
407
445
  // Post-sync commands
408
446
  if (!opts.ignorePostSync) {
409
447
  for (const cmd of context.config.postSyncCommands || []) {
@@ -418,6 +456,30 @@ export async function sync(
418
456
  }
419
457
  }
420
458
 
459
+ async function checkExternalPkgs(
460
+ context: PlasmicContext,
461
+ baseDir: string,
462
+ opts: SyncArgs,
463
+ pkgs: string[]
464
+ ) {
465
+ const missingPkgs = pkgs.filter((pkg) => {
466
+ const installedPkg = findInstalledVersion(context, baseDir, pkg);
467
+ return !installedPkg;
468
+ });
469
+ if (missingPkgs.length > 0) {
470
+ const upgrade = await confirmWithUser(
471
+ `The following packages aren't installed but are required by some projects, would you like to install them? ${missingPkgs.join(
472
+ ", "
473
+ )}`,
474
+ opts.yes
475
+ );
476
+
477
+ if (upgrade) {
478
+ installUpgrade(missingPkgs.join(" "), baseDir);
479
+ }
480
+ }
481
+ }
482
+
421
483
  function maybeRenamePathExt(
422
484
  context: PlasmicContext,
423
485
  path: string,
@@ -467,6 +529,9 @@ async function syncProject(
467
529
  dependencies: { [projectId: string]: string },
468
530
  summary: Map<string, ComponentUpdateSummary>,
469
531
  pendingMerge: ComponentPendingMerge[],
532
+ indirect: boolean,
533
+ externalNpmPackages: Set<string>,
534
+ externalCssImports: Set<string>,
470
535
  metadataDefaults?: Metadata
471
536
  ): Promise<void> {
472
537
  const newComponentScheme =
@@ -510,6 +575,7 @@ async function syncProject(
510
575
  },
511
576
  opts.metadata
512
577
  ),
578
+ indirect,
513
579
  });
514
580
 
515
581
  // Convert from TSX => JSX
@@ -568,7 +634,8 @@ async function syncProject(
568
634
  summary,
569
635
  pendingMerge,
570
636
  projectBundle.checksums,
571
- opts.baseDir
637
+ opts.baseDir,
638
+ indirect
572
639
  );
573
640
  syncCodeComponentsMeta(context, projectId, projectBundle.codeComponentMetas);
574
641
  await upsertStyleTokens(context, projectBundle.usedTokens);
@@ -587,6 +654,12 @@ async function syncProject(
587
654
  projectBundle.imageAssets,
588
655
  projectBundle.checksums
589
656
  );
657
+ (projectBundle.usedNpmPackages || []).forEach((pkg) =>
658
+ externalNpmPackages.add(pkg)
659
+ );
660
+ (projectBundle.externalCssImports || []).forEach((css) =>
661
+ externalCssImports.add(css)
662
+ );
590
663
  }
591
664
 
592
665
  async function syncStyleConfig(
@@ -617,7 +690,8 @@ async function syncProjectConfig(
617
690
  summary: Map<string, ComponentUpdateSummary>,
618
691
  pendingMerge: ComponentPendingMerge[],
619
692
  checksums: ChecksumBundle,
620
- baseDir: string
693
+ baseDir: string,
694
+ indirect: boolean
621
695
  ) {
622
696
  const defaultCssFilePath = defaultResourcePath(
623
697
  context,
@@ -637,6 +711,7 @@ async function syncProjectConfig(
637
711
  projectName: projectBundle.projectName,
638
712
  version,
639
713
  cssFilePath: defaultCssFilePath,
714
+ indirect,
640
715
  })
641
716
  );
642
717
 
@@ -645,7 +720,6 @@ async function syncProjectConfig(
645
720
  if (!projectConfig.cssFilePath) {
646
721
  projectConfig.cssFilePath = defaultCssFilePath;
647
722
  }
648
- projectConfig.projectApiToken = projectApiToken;
649
723
 
650
724
  // plasmic.lock
651
725
  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">;
@@ -158,6 +161,7 @@ export interface ProjectIdAndToken {
158
161
  }
159
162
 
160
163
  export class PlasmicApi {
164
+ private codegenVersion?: string;
161
165
  constructor(private auth: AuthConfig) {}
162
166
 
163
167
  async genStyleConfig(styleOpts?: StyleConfig): Promise<StyleConfigResponse> {
@@ -210,6 +214,16 @@ export class PlasmicApi {
210
214
  return { ...resp.data } as RequiredPackages;
211
215
  }
212
216
 
217
+ async latestCodegenVersion(): Promise<string> {
218
+ if (!this.codegenVersion) {
219
+ const resp = await this.post(
220
+ `${this.codegenHost}/api/v1/code/latest-codegen-version`
221
+ );
222
+ this.codegenVersion = resp.data as string;
223
+ }
224
+ return this.codegenVersion;
225
+ }
226
+
213
227
  /**
214
228
  * Code-gen endpoint.
215
229
  * This will fetch components at an exact specified version.
@@ -235,6 +249,7 @@ export class PlasmicApi {
235
249
  stylesOpts: StyleConfig;
236
250
  codeOpts: CodeConfig;
237
251
  checksums: ChecksumBundle;
252
+ indirect: boolean;
238
253
  metadata?: Metadata;
239
254
  }
240
255
  ): 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
 
@@ -310,6 +318,8 @@ export interface ProjectLock {
310
318
  lang: "ts" | "js";
311
319
  // One for each file whose checksum is computed
312
320
  fileLocks: FileLock[];
321
+ // The version of Codegen when this project was written
322
+ codegenVersion?: string;
313
323
  }
314
324
 
315
325
  export interface PlasmicLock {
@@ -519,6 +529,7 @@ export function getOrAddProjectConfig(
519
529
  icons: [],
520
530
  images: [],
521
531
  jsBundleThemes: [],
532
+ indirect: false,
522
533
  };
523
534
  context.config.projects.push(project);
524
535
  }
@@ -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,22 @@ 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;
70
+
71
+ // If the codegen version on-disk is invalid, we will sync again the project.
72
+ const checkCodegenVersion = async (): Promise<boolean> => {
73
+ const projectLock = context.lock.projects.find(
74
+ (p) => p.projectId === projectId
75
+ );
76
+
77
+ if (!!projectLock?.codegenVersion && semver.gte(projectLock.codegenVersion, await context.api.latestCodegenVersion())) {
78
+ return false;
79
+ }
80
+
81
+ return true;
82
+ };
83
+
84
+ const isOnDiskCodeInvalid = await checkCodegenVersion();
64
85
 
65
86
  // Checks newVersion against plasmic.lock
66
87
  const checkVersionLock = async (): Promise<boolean> => {
@@ -81,9 +102,11 @@ async function checkProjectMeta(
81
102
  ) {
82
103
  // If this is a dependency (not root), and we're dealing with latest dep version
83
104
  // 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
- );
105
+ if (!isOnDiskCodeInvalid) {
106
+ logger.warn(
107
+ `'${root.projectName}' depends on ${projectName}@${newVersion}. To update this project, explicitly specify this project for sync. Skipping...`
108
+ );
109
+ }
87
110
  return false;
88
111
  }
89
112
 
@@ -105,9 +128,11 @@ async function checkProjectMeta(
105
128
  );
106
129
  return true;
107
130
  } else {
108
- logger.info(
109
- `Project '${projectName}'@${newVersion} is already up to date; skipping. (To force an update, run again with "--force")`
110
- );
131
+ if (!isOnDiskCodeInvalid) {
132
+ logger.info(
133
+ `Project '${projectName}'@${newVersion} is already up to date; skipping. (To force an update, run again with "--force")`
134
+ );
135
+ }
111
136
  return false;
112
137
  }
113
138
  }
@@ -177,6 +202,24 @@ async function checkProjectMeta(
177
202
  );
178
203
  };
179
204
 
205
+ const checkIndirect = async (): Promise<boolean> => {
206
+ const projectConfig = context.config.projects.find(
207
+ (p) => p.projectId === projectId
208
+ );
209
+ const configIndirect = projectConfig?.indirect;
210
+ if (configIndirect && !indirect) {
211
+ logger.warn(
212
+ `'${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.`
213
+ );
214
+ return await confirmWithUser(
215
+ "Do you want to confirm it?",
216
+ opts.force || opts.yes,
217
+ "n"
218
+ );
219
+ }
220
+ return true;
221
+ };
222
+
180
223
  const projectIds =
181
224
  opts.projects.length > 0
182
225
  ? opts.projects
@@ -187,8 +230,29 @@ async function checkProjectMeta(
187
230
  // we should always sync it, even if nothing has changed
188
231
  return true;
189
232
  }
233
+ const checkedVersion =
234
+ (await checkVersionLock()) &&
235
+ (await checkVersionRange()) &&
236
+ (await checkIndirect());
190
237
 
191
- return (await checkVersionLock()) && (await checkVersionRange());
238
+ if(!checkedVersion && isOnDiskCodeInvalid) {
239
+ // sync, but try to keep the current version on disk
240
+ const projectLock = context.lock.projects.find(
241
+ (p) => p.projectId === projectId
242
+ );
243
+ const versionOnDisk = projectLock?.version;
244
+ logger.warn(
245
+ `Project '${projectName}' was synced by an incompatible version of Plasmic Codegen. Syncing again on the same version ${projectName}@${versionOnDisk}`
246
+ );
247
+
248
+ meta.version = versionOnDisk ?? meta.version;
249
+ return true;
250
+ } else if (checkedVersion) {
251
+ // sync and upgrade the version
252
+ return true;
253
+ } else {
254
+ return false;
255
+ }
192
256
  }
193
257
 
194
258
  /**
@@ -216,12 +280,16 @@ export async function checkVersionResolution(
216
280
  ? [root]
217
281
  : walkDependencyTree(root, versionResolution.dependencies).reverse();
218
282
  for (const m of queue) {
219
- // If we haven't seen this yet
220
283
  if (!seen.find((p) => p.projectId === m.projectId)) {
221
284
  if (await checkProjectMeta(m, root, context, opts)) {
222
285
  result.push(m);
223
286
  }
224
287
  seen.push(m);
288
+ } else if (root.projectId === m.projectId) {
289
+ // If m is the root project and it was already seen (maybe as a dep
290
+ // for another project), set indirect = false.
291
+ const project = ensure(seen.find((p) => p.projectId === m.projectId));
292
+ project.indirect = false;
225
293
  }
226
294
  }
227
295
  }