@_linked/cli 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +19 -22
  3. package/defaults/app-react-native/README.md +4 -4
  4. package/defaults/app-react-native/apps/mobile/__tests__/composePort.test.ts +33 -0
  5. package/defaults/app-react-native/apps/mobile/__tests__/integration/apiEnv.ts +27 -1
  6. package/defaults/app-react-native/apps/mobile/__tests__/integration/globalSetup.ts +11 -7
  7. package/defaults/app-react-native/apps/mobile/jest.integration.config.js +1 -1
  8. package/defaults/app-react-native/apps/mobile/src/components/PersonOverview.tsx +1 -1
  9. package/defaults/app-react-native/apps/mobile/src/components/PersonPreview.tsx +1 -1
  10. package/defaults/app-react-native/packages/app-shapes/package.json +0 -3
  11. package/lib/esm/cli-methods.d.ts +29 -1
  12. package/lib/esm/cli-methods.d.ts.map +1 -1
  13. package/lib/esm/cli-methods.js +151 -169
  14. package/lib/esm/cli-methods.js.map +1 -1
  15. package/lib/esm/cli.js +18 -8
  16. package/lib/esm/cli.js.map +1 -1
  17. package/lib/esm/commands/start.d.ts +12 -0
  18. package/lib/esm/commands/start.d.ts.map +1 -1
  19. package/lib/esm/commands/start.js +63 -9
  20. package/lib/esm/commands/start.js.map +1 -1
  21. package/lib/esm/utils/esmSpecifiers.d.ts +5 -1
  22. package/lib/esm/utils/esmSpecifiers.d.ts.map +1 -1
  23. package/lib/esm/utils/esmSpecifiers.js +44 -10
  24. package/lib/esm/utils/esmSpecifiers.js.map +1 -1
  25. package/lib/esm/utils.d.ts +16 -7
  26. package/lib/esm/utils.d.ts.map +1 -1
  27. package/lib/esm/utils.js +92 -40
  28. package/lib/esm/utils.js.map +1 -1
  29. package/lib/esm/vite-config.d.ts +17 -0
  30. package/lib/esm/vite-config.d.ts.map +1 -1
  31. package/lib/esm/vite-config.js +42 -8
  32. package/lib/esm/vite-config.js.map +1 -1
  33. package/package.json +2 -2
@@ -14,7 +14,7 @@ import { getEnvFile } from 'env-cmd/dist/get-env-vars.js';
14
14
  import fs from 'fs-extra';
15
15
  import path, { dirname } from 'path';
16
16
  import { createInterface } from 'readline';
17
- import { debugInfo, execp, execPromise, getFileImports, getFiles, getLastCommitTime, getPackageJSON, isImportOutsideOfPackage, isImportWithMissingExtension, isInvalidLINCDImport, needsRebuilding, } from './utils.js';
17
+ import { debugInfo, execp, execPromise, getFileImports, getFiles, getLastCommitTime, getPackageJSON, isImportOutsideOfPackage, isInternalLinkedImport, needsRebuilding, } from './utils.js';
18
18
  import { renameShippedDotfiles } from './utils/shippedDotfiles.js';
19
19
  import { planPackageSetup } from './utils/packageSetup.js';
20
20
  import { rewriteExtensionlessImports } from './utils/esmSpecifiers.js';
@@ -124,7 +124,7 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
124
124
  // Scaffold from the canonical template repo (single source of truth; CN's
125
125
  // server-side createProject clones the same repo). Shallow clone, then drop
126
126
  // .git so the new app starts with clean history.
127
- yield execPromise(`git clone --depth 1 https://github.com/linked-cm/app-template.git "${targetFolder}"`, false, false);
127
+ yield execPromise(`git clone --depth 1 https://github.com/linked-fw/app-template.git "${targetFolder}"`, false, false);
128
128
  fs.removeSync(path.join(targetFolder, '.git'));
129
129
  //make sure the data folder exists (even though its empty).. copying empty folders does not work with fs.copySync
130
130
  fs.mkdirSync(path.join(targetFolder, 'data'), { recursive: true });
@@ -137,19 +137,7 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
137
137
  if (fs.existsSync(gitignoreTemplate)) {
138
138
  fs.renameSync(gitignoreTemplate, path.join(targetFolder, '.gitignore'));
139
139
  }
140
- const yarnrcTemplate = path.join(targetFolder, 'yarnrc.yml.template');
141
- if (fs.existsSync(yarnrcTemplate)) {
142
- fs.renameSync(yarnrcTemplate, path.join(targetFolder, '.yarnrc.yml'));
143
- }
144
- // Mark the new app as a self-contained Yarn project root. Without this,
145
- // Yarn climbs ancestor dirs looking for the project root and may decide
146
- // a stray yarn.lock further up the tree is "the project" — aborting
147
- // install with "the nearest package directory doesn't seem to be part
148
- // of the project declared in <ancestor>".
149
- const yarnLockPath = path.join(targetFolder, 'yarn.lock');
150
- if (!fs.existsSync(yarnLockPath)) {
151
- fs.writeFileSync(yarnLockPath, '');
152
- }
140
+ stripYarnProjectFiles(targetFolder);
153
141
  // Seed the real (gitignored) Layer 2 config from the committed example so
154
142
  // the scaffolded app boots first try. Users can edit either file later.
155
143
  const datasetsExample = path.join(targetFolder, 'linked.backend.datasets.example.json');
@@ -165,17 +153,13 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
165
153
  // create-app stamps this app's identity into the clone directly rather than
166
154
  // relying on ${var} substitution.
167
155
  applyAppIdentity(targetFolder, { appName, appPrefix, hyphenName });
168
- // Pick a package manager. When both are available and the user is running
169
- // create-app interactively, ask. Carry the choice through both the install
170
- // step AND the final "next command" message so the user can copy-paste.
171
- const interactive = !(options.appName ||
172
- options.appPrefix ||
173
- options.appDomain);
174
- const pm = yield choosePackageManager(interactive);
156
+ // New apps are npm apps: npm is the package manager the templates, the CI
157
+ // workflows and the published @_linked/* packages are all tested against, and
158
+ // the react-native template already installs with npm. Keeping a single
159
+ // package manager also keeps the scaffolded app to a single lockfile.
160
+ const pm = CREATE_APP_PACKAGE_MANAGER;
175
161
  if (!options.skipInstall) {
176
- const installCommand = pm === 'yarn'
177
- ? 'export NODE_OPTIONS="--no-network-family-autoselection" && yarn install'
178
- : 'npm install';
162
+ const installCommand = installCommandFor(pm);
179
163
  const spinner = ora({
180
164
  text: `Installing dependencies (${pm})...`,
181
165
  spinner: 'dots',
@@ -207,7 +191,7 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
207
191
  }
208
192
  }
209
193
  }
210
- const startCommand = pm === 'yarn' ? 'yarn start' : 'npm start';
194
+ const startCommand = startCommandFor(pm);
211
195
  log(`Your Linked app is ready at ${chalk.blueBright(targetFolder)}`, `\nStorage: linked.backend.storage.ts wires aliases to stores; linked.backend.datasets.json holds endpoints.`, `Defaults to Fuseki at ${chalk.cyan('http://localhost:3030')} (auto-creates the dataset on first boot).`, `Make sure Fuseki is running, e.g.:`, ` ${chalk.blueBright('docker run -d --rm -p 3030:3030 --name fuseki stain/jena-fuseki')}`, `Edit ${chalk.cyan('linked.backend.datasets.json')} to change endpoints or credentials.`, `Mirror file for the frontend: ${chalk.cyan('src/linked.frontend.storage.ts')} + ${chalk.cyan('src/linked.frontend.datasets.json')}.`, `\nTo start:`, ` ${chalk.blueBright(`cd ${hyphenName} && ${startCommand}`)}`);
212
196
  });
213
197
  /**
@@ -391,9 +375,26 @@ function setEnvVar(envText, key, value) {
391
375
  * - `src/package.ts`: the runtime `linkedPackage` id — shape URIs derive from it.
392
376
  * - pm2 process names + the VS Code launch name (cosmetic, but should match).
393
377
  */
378
+ /**
379
+ * Scaffolded apps are npm apps, so the new app must not also look like a yarn
380
+ * project. Removes every yarn project file a template clone may still carry —
381
+ * including the legacy `yarnrc.yml.template` that create-app used to rename
382
+ * into `.yarnrc.yml`. (No empty `yarn.lock` is written any more either; that
383
+ * file only existed to pin down Yarn's project-root search.)
384
+ */
385
+ export function stripYarnProjectFiles(targetFolder) {
386
+ for (const yarnLeftover of [
387
+ 'yarnrc.yml.template',
388
+ '.yarnrc.yml',
389
+ '.yarn',
390
+ 'yarn.lock',
391
+ ]) {
392
+ fs.removeSync(path.join(targetFolder, yarnLeftover));
393
+ }
394
+ }
394
395
  function applyAppIdentity(targetFolder, ids) {
395
396
  const { appName, appPrefix, hyphenName } = ids;
396
- // 1. .env.example (+ seed .env from it so the first `yarn start` is correct).
397
+ // 1. .env.example (+ seed .env from it so the first `npm start` is correct).
397
398
  const envExample = path.join(targetFolder, '.env.example');
398
399
  if (fs.existsSync(envExample)) {
399
400
  let env = fs.readFileSync(envExample, 'utf8');
@@ -1240,48 +1241,20 @@ const replaceVariablesInFolder = function (folder) {
1240
1241
  const replaceVariablesInFilesWithRoot = function (root, ...files) {
1241
1242
  return replaceVariablesInFiles(...files.map((f) => path.join(root, f)));
1242
1243
  };
1243
- function isAvailable(cmd) {
1244
- return __awaiter(this, void 0, void 0, function* () {
1245
- try {
1246
- const out = (yield execPromise(`${cmd} --version`, false, false));
1247
- return /\d+/.test(String(out));
1248
- }
1249
- catch (_a) {
1250
- return false;
1251
- }
1252
- });
1253
- }
1254
- function detectPackageManagers() {
1255
- return __awaiter(this, void 0, void 0, function* () {
1256
- const found = [];
1257
- if (yield isAvailable('npm'))
1258
- found.push('npm');
1259
- if (yield isAvailable('yarn'))
1260
- found.push('yarn');
1261
- return found;
1262
- });
1244
+ /**
1245
+ * The package manager `create-app` scaffolds with. Both templates (web and
1246
+ * react-native) install with npm, so a scaffolded app has exactly one lockfile.
1247
+ */
1248
+ export const CREATE_APP_PACKAGE_MANAGER = 'npm';
1249
+ /** The shell command that installs a scaffolded app's dependencies. */
1250
+ export function installCommandFor(pm) {
1251
+ return pm === 'yarn'
1252
+ ? 'export NODE_OPTIONS="--no-network-family-autoselection" && yarn install'
1253
+ : 'npm install';
1263
1254
  }
1264
- function choosePackageManager(interactive) {
1265
- return __awaiter(this, void 0, void 0, function* () {
1266
- const available = yield detectPackageManagers();
1267
- if (available.length === 0) {
1268
- throw new Error('Neither npm nor yarn is available on PATH. Install one and retry.');
1269
- }
1270
- if (available.length === 1)
1271
- return available[0];
1272
- if (!interactive) {
1273
- // Match the historical default: prefer yarn when both are installed.
1274
- return available.includes('yarn') ? 'yarn' : 'npm';
1275
- }
1276
- const fallback = available.includes('yarn') ? 'yarn' : 'npm';
1277
- const answer = yield promptUser(`Package manager [${available.join('/')}] (default: ${chalk.gray(fallback)}): `);
1278
- const choice = answer.trim().toLowerCase();
1279
- if (choice === 'npm' || choice === 'yarn') {
1280
- if (available.includes(choice))
1281
- return choice;
1282
- }
1283
- return fallback;
1284
- });
1255
+ /** The command printed to the user for starting their new app. */
1256
+ export function startCommandFor(pm) {
1257
+ return pm === 'yarn' ? 'yarn start' : 'npm start';
1285
1258
  }
1286
1259
  const ensureFolderExists = function (...folders) {
1287
1260
  let target;
@@ -1400,9 +1373,10 @@ export const createComponent = (name_1, ...args_1) => __awaiter(void 0, [name_1,
1400
1373
  log(`Added an import of this file from ${chalk.magenta(indexPath)}`);
1401
1374
  }
1402
1375
  });
1403
- //read the source of all ts/tsx files in the src folder
1404
- //if there is an import that imports a lincd package with /src/ in it, then warn
1405
- //if there is an import that imports something from outside the src folder, then warn
1376
+ // Reads the source of all ts/tsx files in the src folder and fails on imports
1377
+ // that reach outside the package source root ('outside_package'), or that point
1378
+ // into another Linked package's /src/ or /lib/ ('internal_linked'). Extensionless relative
1379
+ // imports are fine: `linked build` rewrites the emitted ESM specifiers.
1406
1380
  export const checkImports = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (sourceFolder = getSourceFolder(), depth = 0, // Used to check if the import is outside the src folder
1407
1381
  invalidImports = new Map()) {
1408
1382
  if (!fs.existsSync(sourceFolder)) {
@@ -1413,12 +1387,8 @@ invalidImports = new Map()) {
1413
1387
  // Start checking each file in the source folder
1414
1388
  for (const file of dir) {
1415
1389
  const filename = path.join(sourceFolder, file);
1416
- // Skip test sources. Test files (and their helpers/probes)
1417
- // are not part of the shipped ESM contract, so the missing-extension rule
1418
- // isn't load-bearing for them; enforcing it only blocks `linked build`
1419
- // (the real ESM-output gate stays in force for shipped source). This is
1420
- // what makes `linked build` green for packages whose tests use
1421
- // extensionless imports (e.g. @_linked/core).
1390
+ // Skip test sources. Test files (and their helpers/probes) are not part
1391
+ // of the shipped package, so the import rules do not apply to them.
1422
1392
  if (/(?:^|[\\/])(?:tests?|__tests__|test-helpers)(?:[\\/]|$)/.test(filename) ||
1423
1393
  /\.(?:test|spec)\.tsx?$/.test(file) ||
1424
1394
  /^type-probe.*\.tsx?$/.test(file)) {
@@ -1432,16 +1402,13 @@ invalidImports = new Map()) {
1432
1402
  if (statSync(filename).isDirectory()) {
1433
1403
  yield checkImports(filename, depth + 1, invalidImports);
1434
1404
  }
1435
- else {
1436
- // Ignore all files that aren't one of the following:
1437
- // - .ts
1438
- // - .tsx
1439
- continue;
1440
- }
1441
1405
  }
1442
1406
  catch (e) {
1443
1407
  console.log(e);
1444
1408
  }
1409
+ // A directory has now been recursed into, and any other file is not a
1410
+ // source file: either way there is nothing here to read imports from.
1411
+ continue;
1445
1412
  }
1446
1413
  const allImports = yield getFileImports(filename);
1447
1414
  if (!invalidImports.has(filename)) {
@@ -1454,15 +1421,9 @@ invalidImports = new Map()) {
1454
1421
  importPath: i,
1455
1422
  });
1456
1423
  }
1457
- if (isInvalidLINCDImport(i, depth)) {
1424
+ if (isInternalLinkedImport(i)) {
1458
1425
  invalidImports.get(filename).push({
1459
- type: 'lincd',
1460
- importPath: i,
1461
- });
1462
- }
1463
- if (isImportWithMissingExtension(i)) {
1464
- invalidImports.get(filename).push({
1465
- type: 'missing_extension',
1426
+ type: 'internal_linked',
1466
1427
  importPath: i,
1467
1428
  });
1468
1429
  }
@@ -1481,26 +1442,22 @@ invalidImports = new Map()) {
1481
1442
  if (type === 'outside_package') {
1482
1443
  message += ' which is outside the package source root';
1483
1444
  }
1484
- if (type === 'lincd') {
1485
- message +=
1486
- ' which should not contain /src/ or /lib/ in the import path';
1487
- }
1488
- if (type === 'missing_extension') {
1445
+ if (type === 'internal_linked') {
1489
1446
  message +=
1490
- ' which should end with a file extension. Like .js or .scss';
1447
+ " which reaches into another Linked package's /src/ or /lib/." +
1448
+ ' That breaks when its build layout changes and bypasses its' +
1449
+ ' exports map. Import its public subpath instead.';
1491
1450
  }
1492
1451
  res += chalk.red(message + '\n');
1493
1452
  });
1494
1453
  });
1495
- // Throw so buildStep aborts the compile pipeline — relative imports MUST
1496
- // have .js (or .scss) extensions for native-ESM-compatible output.
1454
+ // Throw so buildStep aborts the compile pipeline: importing from outside
1455
+ // the package, or into another Linked package's /src/ or /lib/, breaks the
1456
+ // published package.
1497
1457
  throw res;
1498
1458
  }
1499
- else if (depth === 0 && invalidImports.size === 0) {
1500
- // console.info('All imports OK');
1501
- // process.exit(0);
1502
- return true;
1503
- }
1459
+ // Nothing invalid: all imports OK.
1460
+ return true;
1504
1461
  });
1505
1462
  export const depCheckStaged = () => __awaiter(void 0, void 0, void 0, function* () {
1506
1463
  console.log('Checking dependencies of staged files');
@@ -2202,7 +2159,6 @@ export const register = function (registryURL) {
2202
2159
  }
2203
2160
  };
2204
2161
  export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0, [target_1, target2_1, ...args_1], void 0, function* (target, target2, packagePath = process.cwd(), logResults = true) {
2205
- var _a;
2206
2162
  // Ensure packagePath is absolute and points to a directory containing package.json; if not, go up until we find one
2207
2163
  // First, resolve to absolute path
2208
2164
  let currentPath = path.isAbsolute(packagePath)
@@ -2276,7 +2232,20 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2276
2232
  }
2277
2233
  return previousResult && true;
2278
2234
  }
2279
- else if (typeof stepResult === 'object' && stepResult.error) {
2235
+ else if (
2236
+ //a step may report a detail alongside success, e.g. how many files
2237
+ //it changed. Not a warning: the build stays clean.
2238
+ stepResult &&
2239
+ typeof stepResult === 'object' &&
2240
+ stepResult.info) {
2241
+ if (logResults) {
2242
+ spinner.succeed(step.name + ' - ' + stepResult.info);
2243
+ }
2244
+ return previousResult && true;
2245
+ }
2246
+ else if (stepResult &&
2247
+ typeof stepResult === 'object' &&
2248
+ stepResult.error) {
2280
2249
  if (logResults) {
2281
2250
  spinner.fail(step.name + ' - ' + stepResult.error);
2282
2251
  spinner.stop();
@@ -2287,13 +2256,21 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2287
2256
  //failed and should stop
2288
2257
  return false;
2289
2258
  }
2259
+ //anything else — notably a literal false, which several steps return
2260
+ //on failure — is a failure. Name the step, so the build does not end
2261
+ //on a bare 'Build failed'.
2262
+ const failure = step.name + ' failed';
2263
+ if (logResults) {
2264
+ spinner.fail(failure);
2265
+ spinner.stop();
2266
+ }
2267
+ else {
2268
+ console.error(chalk.red(failure));
2269
+ }
2270
+ return false;
2290
2271
  });
2291
2272
  });
2292
2273
  };
2293
- if (((_a = pkgJson.linked) === null || _a === void 0 ? void 0 : _a.extensionlessImports) === true) {
2294
- const skipped = "Skipping the import check: package.json sets 'linked.extensionlessImports'";
2295
- logResults ? spinner.info(skipped) : console.log(skipped);
2296
- }
2297
2274
  planBuildSteps(pkgJson, packagePath).forEach(buildStep);
2298
2275
  let success = yield buildProcess.catch((err) => {
2299
2276
  let msg = typeof err === 'string' || err instanceof Error
@@ -2306,22 +2283,21 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2306
2283
  symbol: chalk.red('✖'),
2307
2284
  // text: 'Build failed',
2308
2285
  });
2286
+ console.log(msg);
2309
2287
  }
2310
2288
  else {
2311
2289
  console.error(chalk.red(packagePath.split('/').pop(), ' - Build failed:'));
2312
2290
  console.error(err);
2313
- return msg;
2314
2291
  }
2315
- console.log(msg);
2292
+ //a failed build always resolves to false, never to a truthy message:
2293
+ //callers decide success by the return value.
2294
+ return false;
2316
2295
  });
2317
- //will be undefined if there was an error
2318
- if (typeof success !== 'undefined' && success !== false) {
2296
+ if (success === true) {
2319
2297
  if (logResults) {
2320
2298
  spinner.stopAndPersist({
2321
2299
  symbol: chalk.greenBright('✔'),
2322
- text: success === true && !warned
2323
- ? 'Build successful'
2324
- : 'Build successful with warnings',
2300
+ text: warned ? 'Build successful with warnings' : 'Build successful',
2325
2301
  });
2326
2302
  }
2327
2303
  }
@@ -2333,52 +2309,31 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2333
2309
  });
2334
2310
  }
2335
2311
  }
2336
- return success;
2312
+ //true only when every step succeeded (warnings included); false otherwise.
2313
+ return success === true;
2337
2314
  });
2338
- // The ordered `linked build` steps for a linkedPackage. A package whose source
2339
- // uses extensionless relative imports (`"linked": {"extensionlessImports": true}`)
2340
- // skips the import check and gets `.js` added to its emitted ESM specifiers.
2341
- export const planBuildSteps = (pkgJson, packagePath) => {
2342
- var _a;
2343
- const extensionlessImports = ((_a = pkgJson.linked) === null || _a === void 0 ? void 0 : _a.extensionlessImports) === true;
2344
- const steps = [];
2345
- if (!extensionlessImports) {
2346
- steps.push({
2347
- name: 'Checking imports',
2348
- apply: () => checkImports(packagePath + '/src'),
2349
- });
2350
- }
2351
- steps.push({
2315
+ // The ordered `linked build` steps for a linkedPackage. Source may use
2316
+ // extensionless relative imports: once `lib/esm` holds everything it will ship,
2317
+ // every package gets `.js` added to the relative specifiers in it (JS and
2318
+ // declarations). For a package that already writes `.js` this is a no-op.
2319
+ export const planBuildSteps = (pkgJson, packagePath) => [
2320
+ {
2321
+ name: 'Checking imports',
2322
+ apply: () => checkImports(packagePath + '/src'),
2323
+ },
2324
+ {
2352
2325
  name: 'Compiling ESM',
2353
2326
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2354
2327
  return compilePackageESM(packagePath);
2355
2328
  }),
2356
- });
2357
- if (extensionlessImports) {
2358
- steps.push({
2359
- name: 'Rewriting ESM import specifiers',
2360
- apply: () => __awaiter(void 0, void 0, void 0, function* () {
2361
- const libEsm = path.join(packagePath, 'lib', 'esm');
2362
- if (!fs.existsSync(libEsm)) {
2363
- return {
2364
- error: "lib/esm was not emitted. 'linked.extensionlessImports' needs an ESM build: add tsconfig-esm.json to the package.",
2365
- };
2366
- }
2367
- const { unresolved } = yield rewriteExtensionlessImports(libEsm);
2368
- if (unresolved.length > 0) {
2369
- return `could not resolve ${unresolved.length} import(s), left unchanged: ${unresolved.join(', ')}`;
2370
- }
2371
- return true;
2372
- }),
2373
- });
2374
- }
2375
- steps.push({
2329
+ },
2330
+ {
2376
2331
  name: 'Compiling CJS',
2377
2332
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2378
2333
  return compilePackageCJS(packagePath);
2379
2334
  }),
2380
- });
2381
- steps.push({
2335
+ },
2336
+ {
2382
2337
  name: 'Copying files to lib folder',
2383
2338
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2384
2339
  const files = yield glob(packagePath + '/src/**/*.{json,d.ts,css,scss}');
@@ -2400,8 +2355,8 @@ export const planBuildSteps = (pkgJson, packagePath) => {
2400
2355
  return allResults.every((r) => r === true);
2401
2356
  });
2402
2357
  }),
2403
- });
2404
- steps.push({
2358
+ },
2359
+ {
2405
2360
  name: 'Dual package support',
2406
2361
  apply: () => {
2407
2362
  // Skip if no tsconfig files (e.g. pure-CSS packages).
@@ -2415,19 +2370,48 @@ export const planBuildSteps = (pkgJson, packagePath) => {
2415
2370
  return res === '';
2416
2371
  });
2417
2372
  },
2418
- });
2419
- steps.push({
2373
+ },
2374
+ {
2420
2375
  name: 'Removing old files from lib folder',
2421
2376
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2422
2377
  return removeOldFiles(packagePath);
2423
2378
  }),
2424
- });
2425
- steps.push({
2379
+ },
2380
+ {
2381
+ // MUST run after 'Copying files to lib folder' and after 'Removing old
2382
+ // files from lib folder', because the rewrite resolves each specifier
2383
+ // against the files actually present in lib/esm:
2384
+ // - the copy step brings in the assets (`./x.scss`) and hand-written
2385
+ // `.d.ts` from src; before it, an asset import looks unresolvable (a
2386
+ // spurious "build with warnings") and a copied `.d.ts` is never rewritten;
2387
+ // - the cleanup step deletes stale output; before it, a leftover `x.js`
2388
+ // makes './x' resolve to './x.js' and the cleanup then removes the file
2389
+ // the specifier now points at.
2390
+ name: 'Rewriting ESM import specifiers',
2391
+ apply: () => __awaiter(void 0, void 0, void 0, function* () {
2392
+ // No tsconfig-esm.json means the ESM compile was skipped (e.g. pure-CSS
2393
+ // packages), so there is nothing to rewrite and nothing to fail over.
2394
+ if (!fs.existsSync(path.join(packagePath, 'tsconfig-esm.json'))) {
2395
+ return true;
2396
+ }
2397
+ const libEsm = path.join(packagePath, 'lib', 'esm');
2398
+ if (!fs.existsSync(libEsm)) {
2399
+ return {
2400
+ error: 'lib/esm was not emitted while tsconfig-esm.json is present. Check the ESM compile.',
2401
+ };
2402
+ }
2403
+ const { changed, unresolved } = yield rewriteExtensionlessImports(libEsm);
2404
+ if (unresolved.length > 0) {
2405
+ return `could not resolve ${unresolved.length} import(s), left unchanged: ${unresolved.join(', ')}`;
2406
+ }
2407
+ return changed > 0 ? { info: `rewrote ${changed} file(s)` } : true;
2408
+ }),
2409
+ },
2410
+ {
2426
2411
  name: 'Checking dependencies',
2427
2412
  apply: () => depCheck(packagePath),
2428
- });
2429
- return steps;
2430
- };
2413
+ },
2414
+ ];
2431
2415
  export const compilePackage = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (packagePath = process.cwd()) {
2432
2416
  //echo 'compiling CJS' && tsc -p tsconfig-cjs.json && echo 'compiling ESM' && tsc -p tsconfig-esm.json
2433
2417
  // let cjsConfig = fs.existsSync(path.join(packagePath,'tsconfig-cjs.json'));
@@ -2751,12 +2735,10 @@ export var buildUpdated = function (back_1, target_1, target2_1) {
2751
2735
  // log('path: ' + pathToBuild);
2752
2736
  return buildPackage(null, null, pathToBuild, false)
2753
2737
  .then((res) => {
2754
- //empty string or true is success
2755
- //false is success with warnings
2756
- //any other string is the build error text
2757
- //undefined result means it failed
2758
- if (typeof res === 'undefined' || typeof res === 'string') {
2759
- logError('Failed to build ' + pkg.packageName + '. ' + res ? res : '');
2738
+ //buildPackage returns true only when the build succeeded
2739
+ //(warnings included); anything else is a failure.
2740
+ if (res !== true) {
2741
+ logError('Failed to build ' + pkg.packageName);
2760
2742
  process.exit(1);
2761
2743
  }
2762
2744
  else {