@_linked/cli 1.12.0 → 1.14.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 (68) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +81 -38
  3. package/defaults/app-react-native/README.md +62 -22
  4. package/defaults/app-react-native/apps/mobile/App.tsx +6 -3
  5. package/defaults/app-react-native/apps/mobile/__tests__/env.test.ts +39 -0
  6. package/defaults/app-react-native/apps/mobile/__tests__/integration/apiEnv.ts +49 -0
  7. package/defaults/app-react-native/apps/mobile/__tests__/integration/globalSetup.ts +156 -0
  8. package/defaults/app-react-native/apps/mobile/__tests__/integration/globalTeardown.ts +20 -0
  9. package/defaults/app-react-native/apps/mobile/__tests__/integration/nodeFetchEnvironment.js +13 -0
  10. package/defaults/app-react-native/apps/mobile/__tests__/integration/personOverview.test.tsx +51 -0
  11. package/defaults/app-react-native/apps/mobile/__tests__/integration/restoreNodeFetch.ts +8 -0
  12. package/defaults/app-react-native/apps/mobile/__tests__/{linkedDefaults.test.tsx → nativeDefaults.test.tsx} +5 -7
  13. package/defaults/app-react-native/apps/mobile/__tests__/storage.test.ts +24 -0
  14. package/defaults/app-react-native/apps/mobile/__tests__/stripJsonImportAttributes.test.ts +45 -0
  15. package/defaults/app-react-native/apps/mobile/app.config.ts +21 -0
  16. package/defaults/app-react-native/apps/mobile/babel/stripJsonImportAttributes.js +41 -0
  17. package/defaults/app-react-native/apps/mobile/babel.config.js +13 -0
  18. package/defaults/app-react-native/apps/mobile/jest.integration.config.js +16 -0
  19. package/defaults/app-react-native/apps/mobile/package.json +11 -3
  20. package/defaults/app-react-native/apps/mobile/src/components/PersonOverview.tsx +123 -0
  21. package/defaults/app-react-native/apps/mobile/src/components/PersonOverviewContext.tsx +12 -0
  22. package/defaults/app-react-native/apps/mobile/src/components/PersonPreview.tsx +135 -0
  23. package/defaults/app-react-native/apps/mobile/src/shell/apiPort.json +1 -0
  24. package/defaults/app-react-native/apps/mobile/src/shell/env.ts +30 -0
  25. package/defaults/app-react-native/apps/mobile/src/shell/storage.ts +9 -0
  26. package/defaults/app-react-native/docker-compose.yml +22 -0
  27. package/defaults/app-react-native/eslint.config.js +47 -0
  28. package/defaults/app-react-native/gitignore.template +5 -0
  29. package/defaults/app-react-native/package.json +14 -4
  30. package/defaults/app-react-native/packages/app-shapes/package.json +20 -3
  31. package/defaults/app-react-native/packages/app-shapes/tsconfig-esm.json +12 -0
  32. package/defaults/app-react-native/scripts/check-react.mjs +55 -0
  33. package/defaults/app-react-native/services/api/README.md +50 -2
  34. package/defaults/app-react-native/services/api/env.example.template +11 -0
  35. package/defaults/app-react-native/services/api/linked.backend.datasets.json +15 -0
  36. package/defaults/app-react-native/services/api/linked.backend.storage.ts +35 -0
  37. package/defaults/app-react-native/services/api/package.json +27 -1
  38. package/defaults/app-react-native/services/api/scripts/wait-for-fuseki.mjs +26 -0
  39. package/defaults/app-react-native/services/api/src/backend.ts +9 -0
  40. package/defaults/app-react-native/services/api/src/fileStoreEnv.ts +14 -0
  41. package/defaults/app-react-native/services/api/test/hasS3Env.test.mjs +28 -0
  42. package/defaults/app-react-native/services/api/test/waitForFuseki.test.mjs +27 -0
  43. package/defaults/app-react-native/services/api/tsconfig.json +15 -0
  44. package/lib/esm/cli-methods.d.ts +11 -1
  45. package/lib/esm/cli-methods.d.ts.map +1 -1
  46. package/lib/esm/cli-methods.js +122 -58
  47. package/lib/esm/cli-methods.js.map +1 -1
  48. package/lib/esm/cli.js +10 -6
  49. package/lib/esm/cli.js.map +1 -1
  50. package/lib/esm/commands/start.d.ts +33 -0
  51. package/lib/esm/commands/start.d.ts.map +1 -1
  52. package/lib/esm/commands/start.js +96 -54
  53. package/lib/esm/commands/start.js.map +1 -1
  54. package/lib/esm/interfaces.d.ts +7 -0
  55. package/lib/esm/interfaces.d.ts.map +1 -1
  56. package/lib/esm/utils/esmSpecifiers.d.ts +9 -0
  57. package/lib/esm/utils/esmSpecifiers.d.ts.map +1 -0
  58. package/lib/esm/utils/esmSpecifiers.js +104 -0
  59. package/lib/esm/utils/esmSpecifiers.js.map +1 -0
  60. package/lib/esm/utils/shippedDotfiles.d.ts.map +1 -1
  61. package/lib/esm/utils/shippedDotfiles.js +3 -1
  62. package/lib/esm/utils/shippedDotfiles.js.map +1 -1
  63. package/lib/esm/vite-config.d.ts +42 -0
  64. package/lib/esm/vite-config.d.ts.map +1 -1
  65. package/lib/esm/vite-config.js +185 -38
  66. package/lib/esm/vite-config.js.map +1 -1
  67. package/package.json +1 -1
  68. package/defaults/app-react-native/apps/mobile/src/shell/linkedDefaults.tsx +0 -10
@@ -17,6 +17,7 @@ import { createInterface } from 'readline';
17
17
  import { debugInfo, execp, execPromise, getFileImports, getFiles, getLastCommitTime, getPackageJSON, isImportOutsideOfPackage, isImportWithMissingExtension, isInvalidLINCDImport, needsRebuilding, } from './utils.js';
18
18
  import { renameShippedDotfiles } from './utils/shippedDotfiles.js';
19
19
  import { planPackageSetup } from './utils/packageSetup.js';
20
+ import { rewriteExtensionlessImports } from './utils/esmSpecifiers.js';
20
21
  import { spawn as spawnChild } from 'child_process';
21
22
  import { findNearestPackageJson } from 'find-nearest-package-json';
22
23
  import { statSync } from 'fs';
@@ -39,7 +40,7 @@ let dirname__ = typeof __dirname !== 'undefined'
39
40
  * `scripts/storage-config.js` (pre-rename). The fallback chain lets older
40
41
  * app clones keep booting through the rename.
41
42
  */
42
- // Plan-011: extracted to ./lifecycle.ts so the Vite SSR loader doesn't
43
+ // Extracted to ./lifecycle.ts so the Vite SSR loader doesn't
43
44
  // have to graph-walk the rest of cli-methods.ts (which contains many
44
45
  // dynamic imports Vite can't analyze). We import for internal callers
45
46
  // AND re-export so external consumers can keep importing from here.
@@ -211,7 +212,7 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
211
212
  });
212
213
  /**
213
214
  * iOS bundle identifier from the app domain and prefix:
214
- * ('formaestudios.com', 'formae') -> 'com.formaestudios.formae'.
215
+ * ('example.com', 'demo') -> 'com.example.demo'.
215
216
  */
216
217
  export function reactNativeBundleId(appDomain, appPrefix) {
217
218
  const host = appDomain
@@ -247,6 +248,17 @@ function assertReactNativeScaffoldable(targetFolder, appPrefix) {
247
248
  }
248
249
  /** Placeholder name of the shapes package in defaults/app-react-native. */
249
250
  export const RN_SHAPES_TOKEN = 'app-shapes';
251
+ /**
252
+ * Literal tokens in defaults/app-react-native that are replaced by names
253
+ * derived from the app prefix: the shapes package and the Fuseki datasets.
254
+ */
255
+ export function reactNativeTokens(appPrefix) {
256
+ return {
257
+ [RN_SHAPES_TOKEN]: `${appPrefix}-shapes`,
258
+ 'app-dev': `${appPrefix}-dev`,
259
+ 'app-test': `${appPrefix}-test`,
260
+ };
261
+ }
250
262
  /**
251
263
  * Replace every occurrence of `token` in the text files under `folder`
252
264
  * (node_modules and files containing a NUL byte are skipped). A literal token
@@ -309,8 +321,11 @@ export function scaffoldReactNativeApp(targetFolder_1, id_1) {
309
321
  }
310
322
  // 4a. Rename the shapes package token everywhere: workspaces, .gitignore
311
323
  // negation, package names, dependency keys, Jest patterns, import specifiers,
312
- // test expectations and docs. Only the literal `app-shapes` token is replaced.
313
- replaceTokenInTextFiles(targetFolder, RN_SHAPES_TOKEN, shapesName);
324
+ // test expectations and docs. The Fuseki dataset tokens (`app-dev`,
325
+ // `app-test`) are replaced the same way. Only these literal tokens change.
326
+ for (const [token, value] of Object.entries(reactNativeTokens(appPrefix))) {
327
+ replaceTokenInTextFiles(targetFolder, token, value);
328
+ }
314
329
  // 4b. Stamp the identity fields that are not the shapes token.
315
330
  const rootPkgPath = path.join(targetFolder, 'package.json');
316
331
  if (fs.existsSync(rootPkgPath)) {
@@ -353,7 +368,7 @@ export function scaffoldReactNativeApp(targetFolder_1, id_1) {
353
368
  }
354
369
  }
355
370
  // 6. Next steps.
356
- 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')}`);
371
+ log(`Your Linked React Native app is ready at ${chalk.blueBright(targetFolder)}`, `\nNext steps:`, ` ${chalk.blueBright(`cd ${path.basename(targetFolder)}`)}`, ` ${chalk.blueBright('npm test')} app and API unit tests`, ` ${chalk.blueBright('cp services/api/.env.example services/api/.env')} once`, ` ${chalk.blueBright('npm run fuseki:up')} Fuseki on :3030 (Docker)`, ` ${chalk.blueBright('npm run api')} API-only backend on :4000`, ` ${chalk.blueBright('npm run test:integration')} round trip against Fuseki`, ` ${chalk.blueBright('cd apps/mobile && npx expo run:ios')}`, `See README.md (and services/api/README.md if port 3030 is taken).`);
357
372
  });
358
373
  }
359
374
  // Indirection so tests can replace the scaffold step when exercising createApp.
@@ -1161,7 +1176,7 @@ export const addShapeToBarrel = function (shapeHyphenName, root = process.cwd())
1161
1176
  ensureBackendImportsShapes(root);
1162
1177
  return barrelPath;
1163
1178
  };
1164
- // Exported for Shape-Builder reuse (plan-010 T1d.4): CodeShapeSyncService adds
1179
+ // Exported for Shape-Builder reuse: CodeShapeSyncService adds
1165
1180
  // the `import './shapes/<Shape>.js';` line to a generated app package's index.
1166
1181
  export const addLineToIndex = function (line, insertMatchString, root = process.cwd(), insertAtStart = false) {
1167
1182
  //import ontology in index
@@ -1347,7 +1362,7 @@ export const createShape = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...
1347
1362
  log(`Created a new shape class template in ${chalk.magenta(targetFile.replace(basePath, ''))}`);
1348
1363
  // Register the shape in the shapes barrel (src/shapes/index.ts) — NOT the main index —
1349
1364
  // and make sure the barrel is loaded on both boot paths. This is what makes the app
1350
- // materialize its own shapes on boot (plan-027 / plan-028 handover).
1365
+ // materialize its own shapes on boot.
1351
1366
  const barrelPath = addShapeToBarrel(hyphenName);
1352
1367
  log(`Registered the shape in ${chalk.magenta(barrelPath.replace(basePath, ''))}`);
1353
1368
  });
@@ -1398,7 +1413,7 @@ invalidImports = new Map()) {
1398
1413
  // Start checking each file in the source folder
1399
1414
  for (const file of dir) {
1400
1415
  const filename = path.join(sourceFolder, file);
1401
- // plan-011 §P7 — skip test sources. Test files (and their helpers/probes)
1416
+ // Skip test sources. Test files (and their helpers/probes)
1402
1417
  // are not part of the shipped ESM contract, so the missing-extension rule
1403
1418
  // isn't load-bearing for them; enforcing it only blocks `linked build`
1404
1419
  // (the real ESM-output gate stays in force for shipped source). This is
@@ -1631,6 +1646,13 @@ export const runMethod = (packageName, method, options) => __awaiter(void 0, voi
1631
1646
  .then(() => {
1632
1647
  console.log('Done');
1633
1648
  process.exit();
1649
+ })
1650
+ // An unmatched method (ServerCallError 501) or a provider error rejects; report it instead of an
1651
+ // unhandled rejection.
1652
+ .catch((err) => {
1653
+ var _a;
1654
+ console.error((_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err);
1655
+ process.exit(1);
1634
1656
  });
1635
1657
  });
1636
1658
  }
@@ -2180,6 +2202,7 @@ export const register = function (registryURL) {
2180
2202
  }
2181
2203
  };
2182
2204
  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;
2183
2206
  // Ensure packagePath is absolute and points to a directory containing package.json; if not, go up until we find one
2184
2207
  // First, resolve to absolute path
2185
2208
  let currentPath = path.isAbsolute(packagePath)
@@ -2222,6 +2245,7 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2222
2245
  }).start();
2223
2246
  }
2224
2247
  let buildProcess = Promise.resolve(true);
2248
+ let warned = false;
2225
2249
  let buildStep = (step) => {
2226
2250
  buildProcess = buildProcess.then((previousResult) => {
2227
2251
  if (!previousResult) {
@@ -2235,11 +2259,14 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2235
2259
  //if a build step returns a string,
2236
2260
  //a warning is shown but the build is still successful with warnings
2237
2261
  if (typeof stepResult === 'string') {
2238
- // spinner.text = step.name + ' - ' + stepResult;
2262
+ warned = true;
2239
2263
  if (logResults) {
2240
2264
  spinner.warn(step.name + ' - ' + stepResult);
2241
2265
  spinner.stop();
2242
2266
  }
2267
+ else {
2268
+ console.warn(chalk.yellow(step.name + ' - ' + stepResult));
2269
+ }
2243
2270
  //can still continue
2244
2271
  return true;
2245
2272
  }
@@ -2254,29 +2281,104 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2254
2281
  spinner.fail(step.name + ' - ' + stepResult.error);
2255
2282
  spinner.stop();
2256
2283
  }
2284
+ else {
2285
+ console.error(chalk.red(step.name + ' - ' + stepResult.error));
2286
+ }
2257
2287
  //failed and should stop
2258
2288
  return false;
2259
2289
  }
2260
2290
  });
2261
2291
  });
2262
2292
  };
2263
- buildStep({
2264
- name: 'Checking imports',
2265
- apply: () => checkImports(packagePath + '/src'),
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
+ planBuildSteps(pkgJson, packagePath).forEach(buildStep);
2298
+ let success = yield buildProcess.catch((err) => {
2299
+ let msg = typeof err === 'string' || err instanceof Error
2300
+ ? err.toString()
2301
+ : err.error && !err.error.toString().includes('Command failed:')
2302
+ ? err.error
2303
+ : err.stdout + '\n' + err.stderr;
2304
+ if (logResults) {
2305
+ spinner.stopAndPersist({
2306
+ symbol: chalk.red('✖'),
2307
+ // text: 'Build failed',
2308
+ });
2309
+ }
2310
+ else {
2311
+ console.error(chalk.red(packagePath.split('/').pop(), ' - Build failed:'));
2312
+ console.error(err);
2313
+ return msg;
2314
+ }
2315
+ console.log(msg);
2266
2316
  });
2267
- buildStep({
2317
+ //will be undefined if there was an error
2318
+ if (typeof success !== 'undefined' && success !== false) {
2319
+ if (logResults) {
2320
+ spinner.stopAndPersist({
2321
+ symbol: chalk.greenBright('✔'),
2322
+ text: success === true && !warned
2323
+ ? 'Build successful'
2324
+ : 'Build successful with warnings',
2325
+ });
2326
+ }
2327
+ }
2328
+ else {
2329
+ if (logResults) {
2330
+ spinner.stopAndPersist({
2331
+ symbol: chalk.red('✖'),
2332
+ text: 'Build failed',
2333
+ });
2334
+ }
2335
+ }
2336
+ return success;
2337
+ });
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({
2268
2352
  name: 'Compiling ESM',
2269
2353
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2270
2354
  return compilePackageESM(packagePath);
2271
2355
  }),
2272
2356
  });
2273
- buildStep({
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({
2274
2376
  name: 'Compiling CJS',
2275
2377
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2276
2378
  return compilePackageCJS(packagePath);
2277
2379
  }),
2278
2380
  });
2279
- buildStep({
2381
+ steps.push({
2280
2382
  name: 'Copying files to lib folder',
2281
2383
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2282
2384
  const files = yield glob(packagePath + '/src/**/*.{json,d.ts,css,scss}');
@@ -2299,7 +2401,7 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2299
2401
  });
2300
2402
  }),
2301
2403
  });
2302
- buildStep({
2404
+ steps.push({
2303
2405
  name: 'Dual package support',
2304
2406
  apply: () => {
2305
2407
  // Skip if no tsconfig files (e.g. pure-CSS packages).
@@ -2314,56 +2416,18 @@ export const buildPackage = (target_1, target2_1, ...args_1) => __awaiter(void 0
2314
2416
  });
2315
2417
  },
2316
2418
  });
2317
- buildStep({
2419
+ steps.push({
2318
2420
  name: 'Removing old files from lib folder',
2319
2421
  apply: () => __awaiter(void 0, void 0, void 0, function* () {
2320
2422
  return removeOldFiles(packagePath);
2321
2423
  }),
2322
2424
  });
2323
- buildStep({
2425
+ steps.push({
2324
2426
  name: 'Checking dependencies',
2325
2427
  apply: () => depCheck(packagePath),
2326
2428
  });
2327
- let success = yield buildProcess.catch((err) => {
2328
- let msg = typeof err === 'string' || err instanceof Error
2329
- ? err.toString()
2330
- : err.error && !err.error.toString().includes('Command failed:')
2331
- ? err.error
2332
- : err.stdout + '\n' + err.stderr;
2333
- if (logResults) {
2334
- spinner.stopAndPersist({
2335
- symbol: chalk.red('✖'),
2336
- // text: 'Build failed',
2337
- });
2338
- }
2339
- else {
2340
- console.error(chalk.red(packagePath.split('/').pop(), ' - Build failed:'));
2341
- console.error(err);
2342
- return msg;
2343
- }
2344
- console.log(msg);
2345
- });
2346
- //will be undefined if there was an error
2347
- if (typeof success !== 'undefined' && success !== false) {
2348
- if (logResults) {
2349
- spinner.stopAndPersist({
2350
- symbol: chalk.greenBright('✔'),
2351
- text: success === true
2352
- ? 'Build successful'
2353
- : 'Build successful with warnings',
2354
- });
2355
- }
2356
- }
2357
- else {
2358
- if (logResults) {
2359
- spinner.stopAndPersist({
2360
- symbol: chalk.red('✖'),
2361
- text: 'Build failed',
2362
- });
2363
- }
2364
- }
2365
- return success;
2366
- });
2429
+ return steps;
2430
+ };
2367
2431
  export const compilePackage = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (packagePath = process.cwd()) {
2368
2432
  //echo 'compiling CJS' && tsc -p tsconfig-cjs.json && echo 'compiling ESM' && tsc -p tsconfig-esm.json
2369
2433
  // let cjsConfig = fs.existsSync(path.join(packagePath,'tsconfig-cjs.json'));