@_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
@@ -0,0 +1,47 @@
1
+ // One flat config for every workspace. `npm run lint` runs `eslint .` from the repo root.
2
+ const expoConfig = require('eslint-config-expo/flat');
3
+ const tseslint = require('typescript-eslint');
4
+
5
+ const scope = (configs, files) => [configs].flat().map((config) => ({ ...config, files }));
6
+
7
+ const mobileFiles = ['apps/mobile/**/*.{js,jsx,ts,tsx,mjs,cjs}'];
8
+ const nodeTsFiles = ['services/api/**/*.{ts,tsx,mts}', 'packages/app-shapes/**/*.{ts,tsx,mts}'];
9
+
10
+ module.exports = [
11
+ {
12
+ ignores: [
13
+ '**/node_modules/**',
14
+ '**/lib/**',
15
+ '**/.expo/**',
16
+ '**/ios/**',
17
+ '**/android/**',
18
+ '**/dist/**',
19
+ // Mirrors .gitignore: only the enumerated workspace packages under packages/ are this repo's.
20
+ 'packages/*',
21
+ '!packages/app-shapes',
22
+ ],
23
+ },
24
+
25
+ ...scope(expoConfig, mobileFiles),
26
+ {
27
+ files: mobileFiles,
28
+ rules: {
29
+ // The @_linked/server root barrel pulls express, webpack and react-dom into the bundle.
30
+ 'no-restricted-imports': [
31
+ 'error',
32
+ {
33
+ paths: [
34
+ {
35
+ name: '@_linked/server',
36
+ message:
37
+ 'Import @_linked/server by subpath (e.g. @_linked/server/shapes/quadstores/BackendAPIStore); the root barrel pulls express, webpack and react-dom.',
38
+ },
39
+ ],
40
+ patterns: [{ group: ['@_linked/server/backend*'], message: 'Backend-only entry.' }],
41
+ },
42
+ ],
43
+ },
44
+ },
45
+
46
+ ...scope(tseslint.configs.recommended, nodeTsFiles),
47
+ ];
@@ -4,3 +4,8 @@ packages/*
4
4
  !packages/app-shapes/
5
5
  .expo/
6
6
  dist/
7
+ # Docker Compose overrides (e.g. FUSEKI_PORT); see services/api/README.md.
8
+ /.env
9
+ services/api/.env
10
+ services/api/data/
11
+ packages/app-shapes/lib/
@@ -6,9 +6,19 @@
6
6
  "services/api",
7
7
  "packages/app-shapes"
8
8
  ],
9
- "overrides": {
10
- "@_linked/react": {
11
- "react": "19.2.3"
12
- }
9
+ "scripts": {
10
+ "fuseki:up": "docker compose up -d --wait fuseki",
11
+ "fuseki:down": "docker compose down",
12
+ "api": "npm start -w services/api",
13
+ "check:react": "node scripts/check-react.mjs",
14
+ "lint": "eslint .",
15
+ "typecheck": "tsc --noEmit -p apps/mobile && tsc --noEmit -p services/api && tsc --noEmit -p packages/app-shapes",
16
+ "test": "npm test -w apps/mobile && npm test -w services/api",
17
+ "test:integration": "npm run test:integration -w apps/mobile"
18
+ },
19
+ "devDependencies": {
20
+ "eslint": "9.39.5",
21
+ "eslint-config-expo": "57.0.2",
22
+ "typescript-eslint": "8.70.0"
13
23
  }
14
24
  }
@@ -2,9 +2,26 @@
2
2
  "name": "app-shapes",
3
3
  "version": "0.0.0",
4
4
  "private": true,
5
- "main": "src/index.ts",
6
- "types": "src/index.ts",
5
+ "type": "module",
6
+ "linkedPackage": true,
7
+ "linked": {
8
+ "extensionlessImports": true
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./src/index.ts",
13
+ "react-native": "./src/index.ts",
14
+ "development": "./src/index.ts",
15
+ "default": "./lib/esm/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "linked build"
20
+ },
7
21
  "dependencies": {
8
- "@_linked/core": "2.18.0"
22
+ "@_linked/core": "2.18.1"
23
+ },
24
+ "devDependencies": {
25
+ "@_linked/cli": "1.13.0"
9
26
  }
10
27
  }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": false,
5
+ "declaration": true,
6
+ "module": "esnext",
7
+ "target": "es2018",
8
+ "moduleResolution": "bundler",
9
+ "rootDir": "src",
10
+ "outDir": "lib/esm"
11
+ }
12
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // One React for the app: `react` resolved from apps/mobile, from the installed @_linked/react and from react-native
3
+ // must be the same file. `npm ls react` is not used because @_linked/server and @_linked/server-utils nest their
4
+ // own copies, which the app never loads.
5
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
6
+ import { createRequire } from 'node:module';
7
+ import { dirname, join, resolve } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const EXPECTED_VERSION = '19.2.3';
11
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
12
+ const mobileDir = join(repoRoot, 'apps/mobile');
13
+
14
+ const failures = [];
15
+
16
+ /** Resolves `react`'s entry file as Node would from `fromDir`, following symlinks. */
17
+ function resolveReact(fromDir) {
18
+ const require = createRequire(join(fromDir, 'noop.js'));
19
+ return realpathSync(require.resolve('react'));
20
+ }
21
+
22
+ /** Finds an installed package's directory from `fromDir` by the node_modules walk (works when exports hide package.json). */
23
+ function packageDir(name, fromDir) {
24
+ const require = createRequire(join(fromDir, 'noop.js'));
25
+ for (const base of require.resolve.paths(name) ?? []) {
26
+ const dir = join(base, name);
27
+ if (existsSync(join(dir, 'package.json'))) return realpathSync(dir);
28
+ }
29
+ return null;
30
+ }
31
+
32
+ const appReact = resolveReact(mobileDir);
33
+ const appReactPkg = JSON.parse(readFileSync(join(dirname(appReact), 'package.json'), 'utf8'));
34
+ console.log(`apps/mobile -> react@${appReactPkg.version} ${appReact}`);
35
+ if (appReactPkg.version !== EXPECTED_VERSION) {
36
+ failures.push(`apps/mobile resolves react@${appReactPkg.version}, expected ${EXPECTED_VERSION}.`);
37
+ }
38
+
39
+ for (const name of ['@_linked/react', 'react-native']) {
40
+ const dir = packageDir(name, mobileDir);
41
+ if (!dir) {
42
+ failures.push(`${name} is not installed where apps/mobile can resolve it.`);
43
+ continue;
44
+ }
45
+ const react = resolveReact(dir);
46
+ console.log(`${name.padEnd(20)} -> ${react}`);
47
+ if (react !== appReact) failures.push(`${name} (${dir}) resolves a different react: ${react}`);
48
+ }
49
+
50
+ if (failures.length) {
51
+ console.error('\ncheck:react FAILED: the app would load more than one React.');
52
+ for (const f of failures) console.error(` - ${f}`);
53
+ process.exit(1);
54
+ }
55
+ console.log('\ncheck:react OK: one React for the app.');
@@ -1,4 +1,52 @@
1
1
  # api
2
2
 
3
- Placeholder for the backend service. It is a workspace so the monorepo layout is stable; the server, its
4
- storage configuration and its dependency on the shapes package are added when the backend is built.
3
+ The Linked backend: `@_linked/server` in API-only mode (`linked start --api-only`), with no web frontend. The
4
+ mobile app's `BackendAPIStore` sends DSL queries here; the generic `BackendAPIStoreProvider` forwards them to
5
+ Fuseki. Shapes come from `app-shapes`, which Vite loads from source in development.
6
+
7
+ ## Run
8
+
9
+ ```sh
10
+ cp services/api/.env.example services/api/.env # once
11
+ npm run fuseki:up # from the repo root; Docker Compose Fuseki on :3030
12
+ npm run api # API on :4000; GET / is 404 by design
13
+ ```
14
+
15
+ `npm run api` checks that Fuseki answers first and exits with "Run `npm run fuseki:up`" if it does not.
16
+
17
+ **Port 3030 taken** (for example by another project's Fuseki): pick another host port in two places.
18
+ 1. A root `.env` (gitignored; Docker Compose reads it automatically): `FUSEKI_PORT=3031`.
19
+ 2. `services/api/.env`: `FUSEKI_BASE_URL=http://localhost:3031`.
20
+
21
+ The integration tests read `FUSEKI_BASE_URL`, or `FUSEKI_PORT`, from the shell environment
22
+ (`FUSEKI_PORT=3031 npm run test:integration`), then from `services/api/.env`. They refuse to reset anything unless
23
+ that port is the one Docker Compose publishes for this repo's `fuseki` service.
24
+
25
+ ## Backend export
26
+
27
+ `package.json` exports `./backend` under the `development` condition only (`src/backend.ts`), which is what
28
+ `linked start` uses. A production start is not set up yet.
29
+
30
+ ## Storage
31
+
32
+ - **Graph:** `linked.backend.datasets.json` configures the `appData` Fuseki store. `FUSEKI_DATASET` picks the
33
+ dataset (`app-dev`) and `FUSEKI_DB_TYPE` its type (`tdb2`, or `mem` for tests); it is created on startup.
34
+ The integration tests use the in-memory `app-test` dataset and reset it on every run.
35
+ - **Files:** a `LocalFileStore` in `services/api/data/uploads/` by default (the path is relative to the working
36
+ directory, which is `services/api` under `npm run api`). When all of `AWS_ACCESS_KEY_ID`,
37
+ `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `S3_BUCKET_ENDPOINT` and `S3_FILES_BUCKET_NAME` are set, an `S3FileStore`
38
+ (S3, or an S3-compatible service such as DigitalOcean Spaces) is used instead. The S3 branch is not yet verified against a real bucket.
39
+
40
+ ## Dependencies
41
+
42
+ `react-dom` is a direct dependency although the API renders no pages: `LinkedServer` imports `react-dom/server`
43
+ at load time, and the root React is 19.2.3 (for `apps/mobile`), so `@_linked/server`'s own `react-dom` 18 is
44
+ nested where Vite SSR cannot resolve it. Pinning `react-dom` 19.2.3 here hoists it next to the root React.
45
+
46
+ `linked start` finds the hoisted `app-shapes` workspace and runs in workspace mode: Vite loads `app-shapes` from
47
+ `src` through its `development` export condition, while published packages such as `@_linked/core` and
48
+ `@_linked/fuseki` stay external, so the stores `loadStores` imports share one core instance.
49
+
50
+ ## Test
51
+
52
+ `npm test -w services/api` runs the unit tests with `node --test`.
@@ -0,0 +1,11 @@
1
+ PORT=4000
2
+ SITE_ROOT=http://localhost:4000
3
+ DATA_ROOT=http://localhost:4000/data
4
+ NODE_ENV=development
5
+ FUSEKI_BASE_URL=http://localhost:3030
6
+ FUSEKI_DATASET=app-dev
7
+ FUSEKI_DB_TYPE=tdb2
8
+ FUSEKI_USER=admin
9
+ FUSEKI_PASSWORD=admin
10
+ # Optional, selects S3FileStore (S3 or an S3-compatible service such as DigitalOcean Spaces) when all are set:
11
+ # AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_REGION= S3_BUCKET_ENDPOINT= S3_FILES_BUCKET_NAME=
@@ -0,0 +1,15 @@
1
+ {
2
+ "datasets": {
3
+ "appData": {
4
+ "store": "@_linked/fuseki/shapes/FusekiStore",
5
+ "config": {
6
+ "endpoint": "${FUSEKI_BASE_URL:-http://localhost:3030}/${FUSEKI_DATASET:-app-dev}",
7
+ "credentials": {
8
+ "type": "basic",
9
+ "username": "${FUSEKI_USER:-admin}",
10
+ "password": "${FUSEKI_PASSWORD:-admin}"
11
+ }
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,35 @@
1
+ // Loaded by `linked start` (Vite SSR) before the server starts: registers the graph store and the file store.
2
+ import datasetsConfig from './linked.backend.datasets.json' with { type: 'json' };
3
+ import { parseDatasetsConfig } from '@_linked/core/utils/parseDatasetsConfig';
4
+ import { loadStores } from '@_linked/core/utils/loadStores';
5
+ import { LinkedStorage } from '@_linked/core/utils/LinkedStorage';
6
+ import { LinkedFileStorage } from '@_linked/core/utils/LinkedFileStorage';
7
+ import type { FusekiStore } from '@_linked/fuseki/shapes/FusekiStore';
8
+ import { S3FileStore } from '@_linked/s3/shapes/S3FileStore';
9
+ import { LocalFileStore } from '@_linked/server/shapes/filestores/LocalFileStore';
10
+ import { hasS3Env } from './src/fileStoreEnv';
11
+
12
+ const env = process.env;
13
+
14
+ const stores = await loadStores(parseDatasetsConfig(datasetsConfig, env));
15
+ const appData = stores.appData as FusekiStore;
16
+ // Creates the dataset on first start; its type comes from FUSEKI_DB_TYPE (tdb2 for dev, mem for tests).
17
+ // Awaited so the first query never races the creation.
18
+ await appData.ensureDatasetExists();
19
+ LinkedStorage.setDefaultDataset(appData);
20
+
21
+ // The S3 branch is not yet verified against a real bucket.
22
+ // LocalFileStore's name only forms its URI. Its directory is `data/uploads`, relative to the working directory, and
23
+ // `linked start` runs in services/api, so files land in services/api/data/uploads/ (gitignored).
24
+ const fileStore = hasS3Env(env)
25
+ ? new S3FileStore('files', {
26
+ bucketName: env.S3_FILES_BUCKET_NAME,
27
+ clientConfig: {
28
+ endpoint: env.S3_BUCKET_ENDPOINT,
29
+ region: env.AWS_REGION,
30
+ accessKeyId: env.AWS_ACCESS_KEY_ID,
31
+ secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
32
+ },
33
+ })
34
+ : new LocalFileStore(`${env.NODE_ENV || 'development'}-files`);
35
+ LinkedFileStorage.setDefaultDataset(fileStore);
@@ -1,5 +1,31 @@
1
1
  {
2
2
  "name": "api",
3
+ "version": "0.0.0",
3
4
  "private": true,
4
- "version": "0.0.0"
5
+ "type": "module",
6
+ "linkedApp": true,
7
+ "exports": {
8
+ "./backend": {
9
+ "development": "./src/backend.ts"
10
+ }
11
+ },
12
+ "scripts": {
13
+ "start": "node scripts/wait-for-fuseki.mjs && linked start --api-only",
14
+ "test": "node --experimental-strip-types --no-warnings=ExperimentalWarning --test test/*.test.mjs"
15
+ },
16
+ "dependencies": {
17
+ "@_linked/core": "2.18.1",
18
+ "@_linked/fuseki": "3.0.0",
19
+ "@_linked/s3": "1.1.0",
20
+ "@_linked/schema": "1.1.2",
21
+ "@_linked/server": "2.3.0",
22
+ "@_linked/server-utils": "1.2.0",
23
+ "app-shapes": "*",
24
+ "react-dom": "19.2.3"
25
+ },
26
+ "devDependencies": {
27
+ "@_linked/cli": "1.13.0",
28
+ "@types/node": "22.19.1",
29
+ "typescript": "6.0.3"
30
+ }
5
31
  }
@@ -0,0 +1,26 @@
1
+ // Fails fast when Fuseki is down, instead of letting the API or the integration tests time out.
2
+ // Reads FUSEKI_BASE_URL from the environment, then from services/api/.env, then defaults to localhost:3030.
3
+ // WAIT_FOR_FUSEKI_TIMEOUT_MS overrides the 5 s wait (the tests use a short one).
4
+ import { existsSync } from 'node:fs';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const envFile = resolve(dirname(fileURLToPath(import.meta.url)), '../.env');
9
+ if (!process.env.FUSEKI_BASE_URL && existsSync(envFile)) process.loadEnvFile(envFile);
10
+
11
+ const baseUrl = (process.env.FUSEKI_BASE_URL || 'http://localhost:3030').replace(/\/$/, '');
12
+ const pingUrl = `${baseUrl}/$/ping`;
13
+ const deadline = Date.now() + (Number(process.env.WAIT_FOR_FUSEKI_TIMEOUT_MS) || 5000);
14
+
15
+ while (Date.now() < deadline) {
16
+ try {
17
+ const res = await fetch(pingUrl, { signal: AbortSignal.timeout(1000) });
18
+ if (res.ok) process.exit(0);
19
+ } catch {
20
+ // Not up yet; retry until the deadline.
21
+ }
22
+ await new Promise((r) => setTimeout(r, 250));
23
+ }
24
+
25
+ console.error(`Fuseki is not reachable at ${baseUrl}. Run \`npm run fuseki:up\`.`);
26
+ process.exit(1);
@@ -0,0 +1,9 @@
1
+ import { BackendProvider } from '@_linked/server-utils/utils/BackendProvider';
2
+ // Registers the Person shape the app's example components query. LinkedServer auto-loads only the workspace
3
+ // shape packages (app-shapes); a shape from an installed package must be imported here, or the API rejects
4
+ // queries for it ("Cannot resolve shape"). In the web app-template the page render imports it instead.
5
+ import '@_linked/schema/shapes/Person';
6
+
7
+ // No custom methods: the app's DSL queries reach Fuseki through @_linked/server's generic BackendAPIStoreProvider
8
+ // (select/create/update/delete forwarded to LinkedStorage). LinkedServer loads this through exports["./backend"].
9
+ export class Backend extends BackendProvider {}
@@ -0,0 +1,14 @@
1
+ // Pure environment check, kept free of Linked imports so `node --test` can load it without Vite.
2
+
3
+ export const S3_ENV_KEYS = [
4
+ 'AWS_ACCESS_KEY_ID',
5
+ 'AWS_SECRET_ACCESS_KEY',
6
+ 'AWS_REGION',
7
+ 'S3_BUCKET_ENDPOINT',
8
+ 'S3_FILES_BUCKET_NAME',
9
+ ] as const;
10
+
11
+ /** True when every S3 variable is set and non-empty: the API then stores files in S3 (or an S3-compatible service). */
12
+ export function hasS3Env(env: Record<string, string | undefined>): boolean {
13
+ return S3_ENV_KEYS.every((key) => Boolean(env[key]));
14
+ }
@@ -0,0 +1,28 @@
1
+ import assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+
4
+ import { hasS3Env } from '../src/fileStoreEnv.ts';
5
+
6
+ const all = {
7
+ AWS_ACCESS_KEY_ID: 'key',
8
+ AWS_SECRET_ACCESS_KEY: 'secret',
9
+ AWS_REGION: 'us-east-1',
10
+ S3_BUCKET_ENDPOINT: 'https://s3.example.com',
11
+ S3_FILES_BUCKET_NAME: 'app-files',
12
+ };
13
+
14
+ test('false for an empty environment', () => {
15
+ assert.equal(hasS3Env({}), false);
16
+ });
17
+
18
+ test('false when only S3_BUCKET_ENDPOINT is set', () => {
19
+ assert.equal(hasS3Env({ S3_BUCKET_ENDPOINT: all.S3_BUCKET_ENDPOINT }), false);
20
+ });
21
+
22
+ test('false when a variable is empty', () => {
23
+ assert.equal(hasS3Env({ ...all, AWS_REGION: '' }), false);
24
+ });
25
+
26
+ test('true when all five variables are set', () => {
27
+ assert.equal(hasS3Env(all), true);
28
+ });
@@ -0,0 +1,27 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { createServer } from 'node:net';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { test } from 'node:test';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const script = resolve(dirname(fileURLToPath(import.meta.url)), '../scripts/wait-for-fuseki.mjs');
9
+
10
+ /** A port nothing listens on: bind an ephemeral port, then release it. */
11
+ async function closedPort() {
12
+ const server = createServer();
13
+ await new Promise((r) => server.listen(0, '127.0.0.1', r));
14
+ const { port } = server.address();
15
+ await new Promise((r) => server.close(r));
16
+ return port;
17
+ }
18
+
19
+ test('exits 1 with the fuseki:up hint when Fuseki is not reachable', async () => {
20
+ const baseUrl = `http://127.0.0.1:${await closedPort()}`;
21
+ const result = spawnSync(process.execPath, [script], {
22
+ env: { ...process.env, FUSEKI_BASE_URL: baseUrl, WAIT_FOR_FUSEKI_TIMEOUT_MS: '300' },
23
+ encoding: 'utf8',
24
+ });
25
+ assert.equal(result.status, 1);
26
+ assert.match(result.stderr, new RegExp(`Fuseki is not reachable at ${baseUrl}\\. Run \`npm run fuseki:up\`\\.`));
27
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "experimentalDecorators": true,
8
+ "resolveJsonModule": true,
9
+ "allowImportingTsExtensions": true,
10
+ "skipLibCheck": true,
11
+ "noEmit": true,
12
+ "types": ["node"]
13
+ },
14
+ "include": ["src", "linked.backend.storage.ts"]
15
+ }
@@ -24,7 +24,7 @@ export declare const createApp: (name: any, basePath?: string, options?: {
24
24
  }) => Promise<void>;
25
25
  /**
26
26
  * iOS bundle identifier from the app domain and prefix:
27
- * ('formaestudios.com', 'formae') -> 'com.formaestudios.formae'.
27
+ * ('example.com', 'demo') -> 'com.example.demo'.
28
28
  */
29
29
  export declare function reactNativeBundleId(appDomain: string, appPrefix: string): string;
30
30
  /**
@@ -34,6 +34,11 @@ export declare function reactNativeBundleId(appDomain: string, appPrefix: string
34
34
  export declare const RN_PREFIX_PATTERN: RegExp;
35
35
  /** Placeholder name of the shapes package in defaults/app-react-native. */
36
36
  export declare const RN_SHAPES_TOKEN = "app-shapes";
37
+ /**
38
+ * Literal tokens in defaults/app-react-native that are replaced by names
39
+ * derived from the app prefix: the shapes package and the Fuseki datasets.
40
+ */
41
+ export declare function reactNativeTokens(appPrefix: string): Record<string, string>;
37
42
  /**
38
43
  * Scaffold the React Native monorepo from defaults/app-react-native.
39
44
  * Identity is stamped into named fields only; the global ${...} substitution
@@ -89,6 +94,11 @@ export declare const upgradePackages: () => Promise<void>;
89
94
  export declare const createPackage: (name: any, uriBase?: any, basePath?: string) => Promise<void>;
90
95
  export declare const register: (registryURL: any) => Promise<void>;
91
96
  export declare const buildPackage: (target: any, target2: any, packagePath?: string, logResults?: boolean) => Promise<any>;
97
+ type BuildStep = {
98
+ name: string;
99
+ apply: () => Promise<unknown>;
100
+ };
101
+ export declare const planBuildSteps: (pkgJson: any, packagePath: string) => BuildStep[];
92
102
  export declare const compilePackage: (packagePath?: string) => Promise<void>;
93
103
  export declare const compilePackageESM: (packagePath?: string) => Promise<boolean>;
94
104
  export declare const compilePackageCJS: (packagePath?: string) => Promise<boolean | {
@@ -1 +1 @@
1
- {"version":3,"file":"cli-methods.d.ts","sourceRoot":"","sources":["../../src/cli-methods.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli-methods.d.ts","sourceRoot":"","sources":["../../src/cli-methods.ts"],"names":[],"mappings":"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"}