@_linked/cli 1.11.1 → 1.13.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 (84) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +48 -0
  3. package/defaults/app-react-native/README.md +56 -0
  4. package/defaults/app-react-native/apps/mobile/App.tsx +26 -0
  5. package/defaults/app-react-native/apps/mobile/__tests__/linkedDefaults.test.tsx +65 -0
  6. package/defaults/app-react-native/apps/mobile/__tests__/shapes.test.ts +27 -0
  7. package/defaults/app-react-native/apps/mobile/app.json +14 -0
  8. package/defaults/app-react-native/apps/mobile/gitignore.template +41 -0
  9. package/defaults/app-react-native/apps/mobile/index.ts +8 -0
  10. package/defaults/app-react-native/apps/mobile/metro.config.js +17 -0
  11. package/defaults/app-react-native/apps/mobile/package.json +41 -0
  12. package/defaults/app-react-native/apps/mobile/src/shell/linkedDefaults.tsx +10 -0
  13. package/defaults/app-react-native/apps/mobile/tsconfig.json +8 -0
  14. package/defaults/app-react-native/gitignore.template +6 -0
  15. package/defaults/app-react-native/package.json +14 -0
  16. package/defaults/app-react-native/packages/app-shapes/package.json +10 -0
  17. package/defaults/app-react-native/packages/app-shapes/src/index.ts +2 -0
  18. package/defaults/app-react-native/packages/app-shapes/src/package.ts +10 -0
  19. package/defaults/app-react-native/packages/app-shapes/src/shapes/Example.ts +17 -0
  20. package/defaults/app-react-native/packages/app-shapes/tsconfig.json +12 -0
  21. package/defaults/app-react-native/services/api/README.md +4 -0
  22. package/defaults/app-react-native/services/api/package.json +5 -0
  23. package/defaults/package/npmignore.template +6 -0
  24. package/defaults/package/src/backend.ts +5 -0
  25. package/defaults/package/src/data/example-ontology.json +10 -0
  26. package/defaults/package/src/data/example-ontology.json.d.ts +1 -0
  27. package/defaults/package/src/index.ts +8 -0
  28. package/defaults/package/src/ontologies/example-ontology.ts +42 -0
  29. package/defaults/package/src/package.ts +16 -0
  30. package/defaults/package/src/shapes/index.ts +5 -0
  31. package/defaults/package/src/types.ts +4 -0
  32. package/defaults/package/tsconfig.json +17 -0
  33. package/defaults/setup-publish/dual-branch/github/workflows/publish.yml +29 -15
  34. package/lib/esm/cli-methods.d.ts +39 -0
  35. package/lib/esm/cli-methods.d.ts.map +1 -1
  36. package/lib/esm/cli-methods.js +303 -60
  37. package/lib/esm/cli-methods.js.map +1 -1
  38. package/lib/esm/cli.d.ts.map +1 -1
  39. package/lib/esm/cli.js +11 -2
  40. package/lib/esm/cli.js.map +1 -1
  41. package/lib/esm/commands/setup-publish.js +23 -2
  42. package/lib/esm/commands/setup-publish.js.map +1 -1
  43. package/lib/esm/commands/start.d.ts +33 -0
  44. package/lib/esm/commands/start.d.ts.map +1 -1
  45. package/lib/esm/commands/start.js +90 -48
  46. package/lib/esm/commands/start.js.map +1 -1
  47. package/lib/esm/interfaces.d.ts +7 -0
  48. package/lib/esm/interfaces.d.ts.map +1 -1
  49. package/lib/esm/utils/esmSpecifiers.d.ts +3 -0
  50. package/lib/esm/utils/esmSpecifiers.d.ts.map +1 -0
  51. package/lib/esm/utils/esmSpecifiers.js +95 -0
  52. package/lib/esm/utils/esmSpecifiers.js.map +1 -0
  53. package/lib/esm/utils/packageSetup.d.ts +19 -0
  54. package/lib/esm/utils/packageSetup.d.ts.map +1 -0
  55. package/lib/esm/utils/packageSetup.js +23 -0
  56. package/lib/esm/utils/packageSetup.js.map +1 -0
  57. package/lib/esm/utils/shippedDotfiles.d.ts +2 -0
  58. package/lib/esm/utils/shippedDotfiles.d.ts.map +1 -0
  59. package/lib/esm/utils/shippedDotfiles.js +21 -0
  60. package/lib/esm/utils/shippedDotfiles.js.map +1 -0
  61. package/lib/esm/vite-config.d.ts +56 -0
  62. package/lib/esm/vite-config.d.ts.map +1 -1
  63. package/lib/esm/vite-config.js +185 -34
  64. package/lib/esm/vite-config.js.map +1 -1
  65. package/package.json +9 -1
  66. package/.changeset/README.md +0 -20
  67. package/.changeset/config.json +0 -14
  68. package/.github/workflows/changeset-check.yml +0 -44
  69. package/.github/workflows/ci.yml +0 -51
  70. package/.github/workflows/publish.yml +0 -77
  71. package/.prettierignore +0 -4
  72. package/.prettierrc.json +0 -24
  73. package/babel.config.cjs +0 -11
  74. package/docs/architecture/vite-runtime-config.md +0 -57
  75. package/jest.config.cjs +0 -33
  76. package/lib-template/cjs/package.json +0 -3
  77. package/lib-template/esm/package.json +0 -3
  78. package/test-results/.last-run.json +0 -6
  79. package/test-results/create-app-Person-fixture-add-/342/206/222-edit-/342/206/222-delete-round-trip/error-context.md +0 -16
  80. package/test-results/create-app-Person-fixture-add-/342/206/222-edit-/342/206/222-delete-round-trip/trace.zip +0 -0
  81. package/tests/e2e/create-app.spec.ts +0 -157
  82. package/tests/unit/safeYarn.test.ts +0 -55
  83. package/tsconfig-cjs.json +0 -10
  84. package/tsconfig-esm.json +0 -10
@@ -15,6 +15,9 @@ import fs from 'fs-extra';
15
15
  import path, { dirname } from 'path';
16
16
  import { createInterface } from 'readline';
17
17
  import { debugInfo, execp, execPromise, getFileImports, getFiles, getLastCommitTime, getPackageJSON, isImportOutsideOfPackage, isImportWithMissingExtension, isInvalidLINCDImport, needsRebuilding, } from './utils.js';
18
+ import { renameShippedDotfiles } from './utils/shippedDotfiles.js';
19
+ import { planPackageSetup } from './utils/packageSetup.js';
20
+ import { rewriteExtensionlessImports } from './utils/esmSpecifiers.js';
18
21
  import { spawn as spawnChild } from 'child_process';
19
22
  import { findNearestPackageJson } from 'find-nearest-package-json';
20
23
  import { statSync } from 'fs';
@@ -60,6 +63,14 @@ function promptUser(question) {
60
63
  });
61
64
  }
62
65
  export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, basePath = process.cwd(), options = {}) {
66
+ // Reject unknown templates before prompting or writing anything.
67
+ const template = options.template;
68
+ if (template !== undefined &&
69
+ template !== 'web' &&
70
+ template !== 'react-native') {
71
+ console.warn(chalk.red(`Unknown template "${template}". Use "web" or "react-native".`));
72
+ return;
73
+ }
63
74
  // If no name provided, prompt for folder name first
64
75
  if (!name) {
65
76
  console.log(chalk.blue('\n📁 Folder name for your app:\n'));
@@ -102,6 +113,11 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
102
113
  setVariable('app_prefix', appPrefix);
103
114
  setVariable('app_domain', appDomain);
104
115
  let targetFolder = path.join(basePath, hyphenName);
116
+ if (template === 'react-native') {
117
+ // Called through reactNativeInternals so tests can stub the scaffold step.
118
+ yield reactNativeInternals.scaffoldReactNativeApp(targetFolder, { appName, appPrefix, appDomain, hyphenName }, { skipInstall: options.skipInstall });
119
+ return;
120
+ }
105
121
  if (!fs.existsSync(targetFolder)) {
106
122
  fs.mkdirSync(targetFolder);
107
123
  }
@@ -194,6 +210,155 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
194
210
  const startCommand = pm === 'yarn' ? 'yarn start' : 'npm start';
195
211
  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}`)}`);
196
212
  });
213
+ /**
214
+ * iOS bundle identifier from the app domain and prefix:
215
+ * ('formaestudios.com', 'formae') -> 'com.formaestudios.formae'.
216
+ */
217
+ export function reactNativeBundleId(appDomain, appPrefix) {
218
+ const host = appDomain
219
+ .trim()
220
+ .toLowerCase()
221
+ .replace(/^[a-z][a-z0-9+.-]*:\/\//, '')
222
+ .split(/[/?#]/)[0]
223
+ .replace(/:\d*$/, '');
224
+ const hostLabels = host.split('.').filter(Boolean);
225
+ // A bare host such as `localhost` gets a `com` top level: com.localhost.<prefix>.
226
+ if (hostLabels.length === 1)
227
+ hostLabels.push('com');
228
+ return [...hostLabels.reverse(), appPrefix.toLowerCase()]
229
+ .map((label) => label.replace(/_/g, '-').replace(/[^a-z0-9-]/g, ''))
230
+ .filter(Boolean)
231
+ .join('.');
232
+ }
233
+ /**
234
+ * The prefix becomes a directory, an npm package name, a bundle-ID label and a
235
+ * literal inside a Jest regex; this form is valid in all four unescaped.
236
+ */
237
+ export const RN_PREFIX_PATTERN = /^[a-z][a-z0-9-]*$/;
238
+ /** Throws when the prefix is invalid or the target folder is non-empty. */
239
+ function assertReactNativeScaffoldable(targetFolder, appPrefix) {
240
+ if (typeof appPrefix !== 'string' || !RN_PREFIX_PATTERN.test(appPrefix)) {
241
+ throw new Error(`Invalid app prefix "${appPrefix !== null && appPrefix !== void 0 ? appPrefix : ''}": it must match ${RN_PREFIX_PATTERN.source} ` +
242
+ '(start with a lowercase letter; only lowercase letters, digits and hyphens).');
243
+ }
244
+ if (fs.existsSync(targetFolder) && fs.readdirSync(targetFolder).length > 0) {
245
+ throw new Error(`Target folder ${targetFolder} already exists and is not empty. ` +
246
+ 'Choose another name or remove the folder.');
247
+ }
248
+ }
249
+ /** Placeholder name of the shapes package in defaults/app-react-native. */
250
+ export const RN_SHAPES_TOKEN = 'app-shapes';
251
+ /**
252
+ * Replace every occurrence of `token` in the text files under `folder`
253
+ * (node_modules and files containing a NUL byte are skipped). A literal token
254
+ * replacement, not the ${...} variable substitution.
255
+ */
256
+ function replaceTokenInTextFiles(folder, token, value) {
257
+ if (token === value)
258
+ return;
259
+ for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
260
+ const full = path.join(folder, entry.name);
261
+ if (entry.isDirectory()) {
262
+ if (entry.name !== 'node_modules' && entry.name !== '.git') {
263
+ replaceTokenInTextFiles(full, token, value);
264
+ }
265
+ }
266
+ else if (entry.isFile()) {
267
+ const buffer = fs.readFileSync(full);
268
+ if (buffer.includes(0))
269
+ continue;
270
+ const text = buffer.toString('utf8');
271
+ if (text.includes(token)) {
272
+ fs.writeFileSync(full, text.split(token).join(value));
273
+ }
274
+ }
275
+ }
276
+ }
277
+ const readJSON = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
278
+ const writeJSON = (file, data) => fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n');
279
+ /**
280
+ * Scaffold the React Native monorepo from defaults/app-react-native.
281
+ * Identity is stamped into named fields only; the global ${...} substitution
282
+ * (replaceVariablesInFolder) is never used, so template literals in TypeScript
283
+ * sources stay intact.
284
+ */
285
+ export function scaffoldReactNativeApp(targetFolder_1, id_1) {
286
+ return __awaiter(this, arguments, void 0, function* (targetFolder, id, options = {}) {
287
+ const { appName, appPrefix, appDomain, hyphenName } = id;
288
+ // Validate before writing anything.
289
+ assertReactNativeScaffoldable(targetFolder, appPrefix);
290
+ // lib/esm/ (built) is two levels below the package root; src/ (Jest) is one.
291
+ const templateDir = options.templateDir ||
292
+ [
293
+ path.join(getScriptDir(), '..', '..', 'defaults', 'app-react-native'),
294
+ path.join(getScriptDir(), '..', 'defaults', 'app-react-native'),
295
+ ].find((dir) => fs.existsSync(dir));
296
+ if (!templateDir) {
297
+ throw new Error('React Native template not found (defaults/app-react-native)');
298
+ }
299
+ const shapesName = `${appPrefix}-shapes`;
300
+ log("Creating new Linked React Native app '" + appName + "'");
301
+ // 1. Copy the template.
302
+ fs.copySync(templateDir, targetFolder);
303
+ // 2. Restore dotfiles npm would strip from the CLI tarball.
304
+ renameShippedDotfiles(targetFolder);
305
+ // 3. Rename the shapes package directory.
306
+ const oldShapesDir = path.join(targetFolder, 'packages', RN_SHAPES_TOKEN);
307
+ const shapesDir = path.join(targetFolder, 'packages', shapesName);
308
+ if (fs.existsSync(oldShapesDir) && oldShapesDir !== shapesDir) {
309
+ fs.renameSync(oldShapesDir, shapesDir);
310
+ }
311
+ // 4a. Rename the shapes package token everywhere: workspaces, .gitignore
312
+ // negation, package names, dependency keys, Jest patterns, import specifiers,
313
+ // test expectations and docs. Only the literal `app-shapes` token is replaced.
314
+ replaceTokenInTextFiles(targetFolder, RN_SHAPES_TOKEN, shapesName);
315
+ // 4b. Stamp the identity fields that are not the shapes token.
316
+ const rootPkgPath = path.join(targetFolder, 'package.json');
317
+ if (fs.existsSync(rootPkgPath)) {
318
+ const rootPkg = readJSON(rootPkgPath);
319
+ rootPkg.name = `${hyphenName}-monorepo`;
320
+ writeJSON(rootPkgPath, rootPkg);
321
+ }
322
+ const mobileDir = path.join(targetFolder, 'apps', 'mobile');
323
+ const appJsonPath = path.join(mobileDir, 'app.json');
324
+ if (fs.existsSync(appJsonPath)) {
325
+ const appJson = readJSON(appJsonPath);
326
+ appJson.expo = appJson.expo || {};
327
+ appJson.expo.name = appName;
328
+ appJson.expo.slug = hyphenName;
329
+ appJson.expo.ios = appJson.expo.ios || {};
330
+ appJson.expo.ios.bundleIdentifier = reactNativeBundleId(appDomain, appPrefix);
331
+ writeJSON(appJsonPath, appJson);
332
+ }
333
+ // 5. Install with npm: the template is an npm workspaces monorepo.
334
+ if (!options.skipInstall) {
335
+ const spinner = ora({
336
+ text: 'Installing dependencies (npm)...',
337
+ spinner: 'dots',
338
+ }).start();
339
+ try {
340
+ yield execPromise('npm install', false, false, {
341
+ cwd: targetFolder,
342
+ maxBuffer: 50 * 1024 * 1024,
343
+ });
344
+ spinner.succeed('Dependencies installed (npm)');
345
+ }
346
+ catch (err) {
347
+ spinner.fail('Could not install dependencies');
348
+ if (err === null || err === void 0 ? void 0 : err.stdout)
349
+ process.stdout.write(err.stdout);
350
+ if (err === null || err === void 0 ? void 0 : err.stderr)
351
+ process.stderr.write(err.stderr);
352
+ throw new Error(`npm install failed. The files are in ${targetFolder}; ` +
353
+ `run \`npm install\` there to retry.`);
354
+ }
355
+ }
356
+ // 6. Next steps.
357
+ log(`Your Linked React Native app is ready at ${chalk.blueBright(targetFolder)}`, `\nNext steps:`, ` ${chalk.blueBright(`cd ${path.basename(targetFolder)}`)}`, ` ${chalk.blueBright('npm test -w apps/mobile')}`, ` ${chalk.blueBright('cd apps/mobile && npx expo run:ios')}`);
358
+ });
359
+ }
360
+ // Indirection so tests can replace the scaffold step when exercising createApp.
361
+ export const reactNativeInternals = { scaffoldReactNativeApp };
197
362
  /** Set or append a `KEY=value` line in a `.env`-style text blob. */
198
363
  function setEnvVar(envText, key, value) {
199
364
  const line = `${key}=${value}`;
@@ -961,6 +1126,42 @@ export const createOntology = (prefix_1, uriBase_1, ...args_1) => __awaiter(void
961
1126
  log(`Added an import of this file from ${chalk.magenta(indexPath)}`);
962
1127
  }
963
1128
  });
1129
+ // The shapes barrel (src/shapes/index.ts) is imported by both the frontend index and
1130
+ // the backend entry, so every shape registers on both boot paths (and materializes into
1131
+ // app-data on boot). `create-shape` adds each new shape here — NOT to the main index.
1132
+ const SHAPES_BARREL_HEADER = `// Shape registry — every shape is imported here so its @linkedShape decorator runs and\n` +
1133
+ `// the shape registers on both boot paths (backend materialization + frontend). \`linked\n` +
1134
+ `// create-shape\` maintains this list.\n//SHAPES\n`;
1135
+ const ensureBackendImportsShapes = function (root = process.cwd()) {
1136
+ const backendPath = path.join(root, 'src', 'backend.ts');
1137
+ if (!fs.existsSync(backendPath))
1138
+ return;
1139
+ let contents = fs.readFileSync(backendPath, 'utf-8');
1140
+ if (contents.includes('./shapes/index'))
1141
+ return;
1142
+ fs.writeFileSync(backendPath, `import './shapes/index.js';\n` + contents);
1143
+ };
1144
+ // Register a shape in the shapes barrel (src/shapes/index.ts), creating it if needed, and
1145
+ // make sure both the main index and backend.ts import the barrel. Idempotent.
1146
+ export const addShapeToBarrel = function (shapeHyphenName, root = process.cwd()) {
1147
+ const shapesDir = path.join(root, 'src', 'shapes');
1148
+ fs.ensureDirSync(shapesDir);
1149
+ const barrelPath = path.join(shapesDir, 'index.ts');
1150
+ let contents = fs.existsSync(barrelPath)
1151
+ ? fs.readFileSync(barrelPath, 'utf-8')
1152
+ : SHAPES_BARREL_HEADER;
1153
+ const line = `import './${shapeHyphenName}.js';`;
1154
+ if (!contents.split(/\n/g).some((l) => l.trim() === line.trim())) {
1155
+ contents = contents.includes('//SHAPES')
1156
+ ? contents.replace('//SHAPES', `//SHAPES\n${line}`)
1157
+ : contents.replace(/\n*$/, '') + `\n${line}\n`;
1158
+ fs.writeFileSync(barrelPath, contents);
1159
+ }
1160
+ // Both boot paths must load the barrel (idempotent).
1161
+ addLineToIndex(`import './shapes/index.js';`, 'shapes', root);
1162
+ ensureBackendImportsShapes(root);
1163
+ return barrelPath;
1164
+ };
964
1165
  // Exported for Shape-Builder reuse (plan-010 T1d.4): CodeShapeSyncService adds
965
1166
  // the `import './shapes/<Shape>.js';` line to a generated app package's index.
966
1167
  export const addLineToIndex = function (line, insertMatchString, root = process.cwd(), insertAtStart = false) {
@@ -1145,12 +1346,11 @@ export const createShape = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...
1145
1346
  //replace variables in some of the copied files
1146
1347
  yield replaceVariablesInFiles(targetFile);
1147
1348
  log(`Created a new shape class template in ${chalk.magenta(targetFile.replace(basePath, ''))}`);
1148
- //if this is NOT a lincd app (but a lincd package)
1149
- let indexPath;
1150
- if (!sourceFolder.includes('frontend')) {
1151
- indexPath = addLineToIndex(`import './shapes/${hyphenName}.js';`, 'shapes');
1152
- log(`Added an import of this file from ${chalk.magenta(indexPath)}`);
1153
- }
1349
+ // Register the shape in the shapes barrel (src/shapes/index.ts) — NOT the main index —
1350
+ // and make sure the barrel is loaded on both boot paths. This is what makes the app
1351
+ // materialize its own shapes on boot (plan-027 / plan-028 handover).
1352
+ const barrelPath = addShapeToBarrel(hyphenName);
1353
+ log(`Registered the shape in ${chalk.magenta(barrelPath.replace(basePath, ''))}`);
1154
1354
  });
1155
1355
  export const createSetComponent = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, basePath = process.cwd()) {
1156
1356
  let targetFolder = ensureFolderExists(basePath, 'src', 'components');
@@ -1866,6 +2066,7 @@ export const createPackage = (name_1, uriBase_1, ...args_1) => __awaiter(void 0,
1866
2066
  let { hyphenName, camelCaseName, underscoreName } = setNameVariables(cleanPackageName);
1867
2067
  log("Creating new LINCD package '" + name + "'");
1868
2068
  fs.copySync(path.join(getScriptDir(), '..', '..', 'defaults', 'package'), targetFolder);
2069
+ renameShippedDotfiles(targetFolder);
1869
2070
  //replace variables in some of the copied files
1870
2071
  yield Promise.all([
1871
2072
  'src/index.ts',
@@ -1896,12 +2097,25 @@ export const createPackage = (name_1, uriBase_1, ...args_1) => __awaiter(void 0,
1896
2097
  console.log('yarn probably not working');
1897
2098
  return '';
1898
2099
  }));
1899
- let installCommand = version.toString().match(/[0-9]+/)
1900
- ? 'yarn install'
1901
- : 'npm install';
1902
- yield execp(`cd ${targetFolder} && ${installCommand} && npm exec lincd build`, true).catch((err) => {
1903
- console.warn('Could not install dependencies');
2100
+ const setup = planPackageSetup(version.toString(), path.join(getScriptDir(), 'launch.js'));
2101
+ if (setup.yarnrc) {
2102
+ fs.writeFileSync(path.join(targetFolder, '.yarnrc.yml'), setup.yarnrc);
2103
+ }
2104
+ const installed = yield execp(setup.installCommand, true, false, {
2105
+ cwd: targetFolder,
2106
+ })
2107
+ .then(() => true)
2108
+ .catch(() => {
2109
+ console.warn(`Could not install dependencies (${setup.installCommand})`);
2110
+ process.exitCode = 1;
2111
+ return false;
1904
2112
  });
2113
+ if (installed) {
2114
+ yield execp(setup.buildCommand, true, false, { cwd: targetFolder }).catch(() => {
2115
+ console.warn('Dependencies installed, but the initial build failed');
2116
+ process.exitCode = 1;
2117
+ });
2118
+ }
1905
2119
  log(`Prepared a new LINCD package in ${chalk.magenta(targetFolder)}`, `Run ${chalk.blueBright('yarn build')} from this directory to build once`, `Or ${chalk.blueBright('yarn dev')} to continuously rebuild on file changes`);
1906
2120
  });
1907
2121
  var getNextVersion = function (version) {
@@ -1967,6 +2181,7 @@ export const register = function (registryURL) {
1967
2181
  }
1968
2182
  };
1969
2183
  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) {
2184
+ var _a;
1970
2185
  // Ensure packagePath is absolute and points to a directory containing package.json; if not, go up until we find one
1971
2186
  // First, resolve to absolute path
1972
2187
  let currentPath = path.isAbsolute(packagePath)
@@ -2047,23 +2262,89 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2047
2262
  });
2048
2263
  });
2049
2264
  };
2050
- buildStep({
2051
- name: 'Checking imports',
2052
- apply: () => checkImports(packagePath + '/src'),
2265
+ if (((_a = pkgJson.linked) === null || _a === void 0 ? void 0 : _a.extensionlessImports) === true) {
2266
+ const skipped = "Skipping the import check: package.json sets 'linked.extensionlessImports'";
2267
+ logResults ? spinner.info(skipped) : console.log(skipped);
2268
+ }
2269
+ planBuildSteps(pkgJson, packagePath).forEach(buildStep);
2270
+ let success = yield buildProcess.catch((err) => {
2271
+ let msg = typeof err === 'string' || err instanceof Error
2272
+ ? err.toString()
2273
+ : err.error && !err.error.toString().includes('Command failed:')
2274
+ ? err.error
2275
+ : err.stdout + '\n' + err.stderr;
2276
+ if (logResults) {
2277
+ spinner.stopAndPersist({
2278
+ symbol: chalk.red('✖'),
2279
+ // text: 'Build failed',
2280
+ });
2281
+ }
2282
+ else {
2283
+ console.error(chalk.red(packagePath.split('/').pop(), ' - Build failed:'));
2284
+ console.error(err);
2285
+ return msg;
2286
+ }
2287
+ console.log(msg);
2053
2288
  });
2054
- buildStep({
2289
+ //will be undefined if there was an error
2290
+ if (typeof success !== 'undefined' && success !== false) {
2291
+ if (logResults) {
2292
+ spinner.stopAndPersist({
2293
+ symbol: chalk.greenBright('✔'),
2294
+ text: success === true
2295
+ ? 'Build successful'
2296
+ : 'Build successful with warnings',
2297
+ });
2298
+ }
2299
+ }
2300
+ else {
2301
+ if (logResults) {
2302
+ spinner.stopAndPersist({
2303
+ symbol: chalk.red('✖'),
2304
+ text: 'Build failed',
2305
+ });
2306
+ }
2307
+ }
2308
+ return success;
2309
+ });
2310
+ // The ordered `linked build` steps for a linkedPackage. A package whose source
2311
+ // uses extensionless relative imports (`"linked": {"extensionlessImports": true}`)
2312
+ // skips the import check and gets `.js` added to its emitted ESM specifiers.
2313
+ export const planBuildSteps = (pkgJson, packagePath) => {
2314
+ var _a;
2315
+ const extensionlessImports = ((_a = pkgJson.linked) === null || _a === void 0 ? void 0 : _a.extensionlessImports) === true;
2316
+ const steps = [];
2317
+ if (!extensionlessImports) {
2318
+ steps.push({
2319
+ name: 'Checking imports',
2320
+ apply: () => checkImports(packagePath + '/src'),
2321
+ });
2322
+ }
2323
+ steps.push({
2055
2324
  name: 'Compiling ESM',
2056
2325
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2057
2326
  return compilePackageESM(packagePath);
2058
2327
  }),
2059
2328
  });
2060
- buildStep({
2329
+ if (extensionlessImports) {
2330
+ steps.push({
2331
+ name: 'Rewriting ESM import specifiers',
2332
+ apply: () => __awaiter(void 0, void 0, void 0, function* () {
2333
+ const libEsm = path.join(packagePath, 'lib', 'esm');
2334
+ if (fs.existsSync(libEsm)) {
2335
+ yield rewriteExtensionlessImports(libEsm);
2336
+ }
2337
+ return true;
2338
+ }),
2339
+ });
2340
+ }
2341
+ steps.push({
2061
2342
  name: 'Compiling CJS',
2062
2343
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2063
2344
  return compilePackageCJS(packagePath);
2064
2345
  }),
2065
2346
  });
2066
- buildStep({
2347
+ steps.push({
2067
2348
  name: 'Copying files to lib folder',
2068
2349
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2069
2350
  const files = yield glob(packagePath + '/src/**/*.{json,d.ts,css,scss}');
@@ -2086,7 +2367,7 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2086
2367
  });
2087
2368
  }),
2088
2369
  });
2089
- buildStep({
2370
+ steps.push({
2090
2371
  name: 'Dual package support',
2091
2372
  apply: () => {
2092
2373
  // Skip if no tsconfig files (e.g. pure-CSS packages).
@@ -2101,56 +2382,18 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2101
2382
  });
2102
2383
  },
2103
2384
  });
2104
- buildStep({
2385
+ steps.push({
2105
2386
  name: 'Removing old files from lib folder',
2106
2387
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2107
2388
  return removeOldFiles(packagePath);
2108
2389
  }),
2109
2390
  });
2110
- buildStep({
2391
+ steps.push({
2111
2392
  name: 'Checking dependencies',
2112
2393
  apply: () => depCheck(packagePath),
2113
2394
  });
2114
- let success = yield buildProcess.catch((err) => {
2115
- let msg = typeof err === 'string' || err instanceof Error
2116
- ? err.toString()
2117
- : err.error && !err.error.toString().includes('Command failed:')
2118
- ? err.error
2119
- : err.stdout + '\n' + err.stderr;
2120
- if (logResults) {
2121
- spinner.stopAndPersist({
2122
- symbol: chalk.red('✖'),
2123
- // text: 'Build failed',
2124
- });
2125
- }
2126
- else {
2127
- console.error(chalk.red(packagePath.split('/').pop(), ' - Build failed:'));
2128
- console.error(err);
2129
- return msg;
2130
- }
2131
- console.log(msg);
2132
- });
2133
- //will be undefined if there was an error
2134
- if (typeof success !== 'undefined' && success !== false) {
2135
- if (logResults) {
2136
- spinner.stopAndPersist({
2137
- symbol: chalk.greenBright('✔'),
2138
- text: success === true
2139
- ? 'Build successful'
2140
- : 'Build successful with warnings',
2141
- });
2142
- }
2143
- }
2144
- else {
2145
- if (logResults) {
2146
- spinner.stopAndPersist({
2147
- symbol: chalk.red('✖'),
2148
- text: 'Build failed',
2149
- });
2150
- }
2151
- }
2152
- return success;
2153
- });
2395
+ return steps;
2396
+ };
2154
2397
  export const compilePackage = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (packagePath = process.cwd()) {
2155
2398
  //echo 'compiling CJS' && tsc -p tsconfig-cjs.json && echo 'compiling ESM' && tsc -p tsconfig-esm.json
2156
2399
  // let cjsConfig = fs.existsSync(path.join(packagePath,'tsconfig-cjs.json'));