@_linked/cli 1.11.1 → 1.12.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 (74) hide show
  1. package/CHANGELOG.md +63 -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 +34 -0
  35. package/lib/esm/cli-methods.d.ts.map +1 -1
  36. package/lib/esm/cli-methods.js +224 -11
  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 +9 -1
  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/utils/packageSetup.d.ts +19 -0
  44. package/lib/esm/utils/packageSetup.d.ts.map +1 -0
  45. package/lib/esm/utils/packageSetup.js +23 -0
  46. package/lib/esm/utils/packageSetup.js.map +1 -0
  47. package/lib/esm/utils/shippedDotfiles.d.ts +2 -0
  48. package/lib/esm/utils/shippedDotfiles.d.ts.map +1 -0
  49. package/lib/esm/utils/shippedDotfiles.js +21 -0
  50. package/lib/esm/utils/shippedDotfiles.js.map +1 -0
  51. package/lib/esm/vite-config.d.ts +14 -0
  52. package/lib/esm/vite-config.d.ts.map +1 -1
  53. package/lib/esm/vite-config.js +7 -3
  54. package/lib/esm/vite-config.js.map +1 -1
  55. package/package.json +9 -1
  56. package/.changeset/README.md +0 -20
  57. package/.changeset/config.json +0 -14
  58. package/.github/workflows/changeset-check.yml +0 -44
  59. package/.github/workflows/ci.yml +0 -51
  60. package/.github/workflows/publish.yml +0 -77
  61. package/.prettierignore +0 -4
  62. package/.prettierrc.json +0 -24
  63. package/babel.config.cjs +0 -11
  64. package/docs/architecture/vite-runtime-config.md +0 -57
  65. package/jest.config.cjs +0 -33
  66. package/lib-template/cjs/package.json +0 -3
  67. package/lib-template/esm/package.json +0 -3
  68. package/test-results/.last-run.json +0 -6
  69. package/test-results/create-app-Person-fixture-add-/342/206/222-edit-/342/206/222-delete-round-trip/error-context.md +0 -16
  70. package/test-results/create-app-Person-fixture-add-/342/206/222-edit-/342/206/222-delete-round-trip/trace.zip +0 -0
  71. package/tests/e2e/create-app.spec.ts +0 -157
  72. package/tests/unit/safeYarn.test.ts +0 -55
  73. package/tsconfig-cjs.json +0 -10
  74. package/tsconfig-esm.json +0 -10
@@ -0,0 +1,5 @@
1
+ // Shape registry — every shape in this package is imported here so its @linkedShape
2
+ // decorator runs and the shape registers on BOTH boot paths: the backend entry
3
+ // (src/backend.ts, which materializes shapes into app-data on boot) and the frontend
4
+ // entry. `linked create-shape` maintains the list below.
5
+ //SHAPES
@@ -0,0 +1,4 @@
1
+ declare module '*.module.css' {
2
+ const classes: {[key: string]: string};
3
+ export default classes;
4
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "sourceMap": true,
4
+ "declaration": true,
5
+ "experimentalDecorators": true,
6
+ "emitDecoratorMetadata": true,
7
+ "downlevelIteration": true,
8
+ "resolveJsonModule": true,
9
+ "esModuleInterop": true,
10
+ "pretty": true,
11
+ "jsx": "react",
12
+ "types": ["node"]
13
+ },
14
+ "files": [
15
+ "./src/types.ts","./src/index.ts"],
16
+ "include": ["./src/backend.ts"]
17
+ }
@@ -136,8 +136,8 @@ jobs:
136
136
  gh pr merge "$pr" --auto --merge \
137
137
  || echo "Auto-merge unavailable (disabled or conflicts) — PR #$pr left open for manual merge."
138
138
 
139
- dev-release:
140
- name: Publish Dev Release
139
+ dev-build:
140
+ name: Verify Dev Build
141
141
  if: github.ref == 'refs/heads/dev'
142
142
  runs-on: ubuntu-latest
143
143
  permissions:
@@ -158,19 +158,33 @@ jobs:
158
158
  - name: Build
159
159
  run: npm run build
160
160
 
161
- - name: Publish next release
162
- run: |
163
- # Start from main's version so changesets apply on top of the latest stable
164
- git fetch origin main --depth=1
165
- MAIN_VERSION=$(git show origin/main:package.json | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version")
166
- npm version "$MAIN_VERSION" --no-git-tag-version --allow-same-version
167
- # Apply pending changesets to get the next version
168
- npx changeset version
169
- NEXT_VERSION=$(node -p "require('./package.json').version")
170
- # Append dev prerelease identifier
171
- DEV_VERSION="${NEXT_VERSION}-next.$(date +%Y%m%d%H%M%S)"
172
- npm version "$DEV_VERSION" --no-git-tag-version
173
- npm publish --tag next
161
+ # Prerelease publishing is commented out, NOT deleted — the machinery is correct and
162
+ # the decision is reversible.
163
+ #
164
+ # It published `X.Y.Z-next.<timestamp>` under the `next` dist-tag on every push to `dev`.
165
+ # The org npm token is now stage-only, so `npm publish` cannot run at all; the nearest
166
+ # equivalent, `npm stage publish`, would queue a version for a maintainer's 2FA approval
167
+ # on EVERY push an approval queue nobody can drain, mostly of artifacts nothing
168
+ # installs. A gate nobody keeps up with is a gate that gets skimmed, so the prerelease
169
+ # goes rather than the gate.
170
+ #
171
+ # To bring it back for a package that genuinely needs prereleases, uncomment and change
172
+ # the last line to `npm stage publish --tag next`. Better still, move this job to
173
+ # `workflow_dispatch` so a prerelease is cut deliberately rather than on every push.
174
+ #
175
+ # - name: Publish next release
176
+ # run: |
177
+ # # Start from main's version so changesets apply on top of the latest stable
178
+ # git fetch origin main --depth=1
179
+ # MAIN_VERSION=$(git show origin/main:package.json | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version")
180
+ # npm version "$MAIN_VERSION" --no-git-tag-version --allow-same-version
181
+ # # Apply pending changesets to get the next version
182
+ # npx changeset version
183
+ # NEXT_VERSION=$(node -p "require('./package.json').version")
184
+ # # Append dev prerelease identifier
185
+ # DEV_VERSION="${NEXT_VERSION}-next.$(date +%Y%m%d%H%M%S)"
186
+ # npm version "$DEV_VERSION" --no-git-tag-version
187
+ # npm publish --tag next
174
188
  env:
175
189
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
176
190
  NODE_AUTH_TOKEN: ${{ secrets.{{NPM_SECRET_NAME}} }}
@@ -8,18 +8,52 @@ import type { PackageDetails } from './interfaces.js';
8
8
  */
9
9
  import { ensureEnvironmentLoaded, loadBackendStorageConfig, getLincdPackages } from './lifecycle.js';
10
10
  export { ensureEnvironmentLoaded, loadBackendStorageConfig, getLincdPackages };
11
+ export type AppTemplate = 'web' | 'react-native';
12
+ type AppIdentity = {
13
+ appName: string;
14
+ appPrefix: string;
15
+ appDomain: string;
16
+ hyphenName: string;
17
+ };
11
18
  export declare const createApp: (name: any, basePath?: string, options?: {
12
19
  appName?: string;
13
20
  appPrefix?: string;
14
21
  appDomain?: string;
15
22
  skipInstall?: boolean;
23
+ template?: AppTemplate;
16
24
  }) => Promise<void>;
25
+ /**
26
+ * iOS bundle identifier from the app domain and prefix:
27
+ * ('formaestudios.com', 'formae') -> 'com.formaestudios.formae'.
28
+ */
29
+ export declare function reactNativeBundleId(appDomain: string, appPrefix: string): string;
30
+ /**
31
+ * The prefix becomes a directory, an npm package name, a bundle-ID label and a
32
+ * literal inside a Jest regex; this form is valid in all four unescaped.
33
+ */
34
+ export declare const RN_PREFIX_PATTERN: RegExp;
35
+ /** Placeholder name of the shapes package in defaults/app-react-native. */
36
+ export declare const RN_SHAPES_TOKEN = "app-shapes";
37
+ /**
38
+ * Scaffold the React Native monorepo from defaults/app-react-native.
39
+ * Identity is stamped into named fields only; the global ${...} substitution
40
+ * (replaceVariablesInFolder) is never used, so template literals in TypeScript
41
+ * sources stay intact.
42
+ */
43
+ export declare function scaffoldReactNativeApp(targetFolder: string, id: AppIdentity, options?: {
44
+ skipInstall?: boolean;
45
+ templateDir?: string;
46
+ }): Promise<void>;
47
+ export declare const reactNativeInternals: {
48
+ scaffoldReactNativeApp: typeof scaffoldReactNativeApp;
49
+ };
17
50
  export declare function warn(...messages: any[]): void;
18
51
  export declare function logError(...errors: any[]): void;
19
52
  export declare function developPackage(target: any, mode: any): void;
20
53
  export declare function runOnPackagesGroupedByDependencies(lincdPackages: any, onBuildStack: (packageGroup: any, dependencies: any) => (pkg: PackageDetails) => Promise<any>, onStackEnd: any, sync?: boolean): Promise<void>;
21
54
  export declare function buildAll(options: any): void;
22
55
  export declare const createOntology: (prefix: any, uriBase?: any, basePath?: string) => Promise<void>;
56
+ export declare const addShapeToBarrel: (shapeHyphenName: string, root?: string) => string;
23
57
  export declare const addLineToIndex: (line: any, insertMatchString: string, root?: string, insertAtStart?: boolean) => string;
24
58
  export declare const setNameVariables: (name: any) => {
25
59
  hyphenName: any;
@@ -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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"}
@@ -15,6 +15,8 @@ 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';
18
20
  import { spawn as spawnChild } from 'child_process';
19
21
  import { findNearestPackageJson } from 'find-nearest-package-json';
20
22
  import { statSync } from 'fs';
@@ -60,6 +62,14 @@ function promptUser(question) {
60
62
  });
61
63
  }
62
64
  export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, basePath = process.cwd(), options = {}) {
65
+ // Reject unknown templates before prompting or writing anything.
66
+ const template = options.template;
67
+ if (template !== undefined &&
68
+ template !== 'web' &&
69
+ template !== 'react-native') {
70
+ console.warn(chalk.red(`Unknown template "${template}". Use "web" or "react-native".`));
71
+ return;
72
+ }
63
73
  // If no name provided, prompt for folder name first
64
74
  if (!name) {
65
75
  console.log(chalk.blue('\n📁 Folder name for your app:\n'));
@@ -102,6 +112,11 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
102
112
  setVariable('app_prefix', appPrefix);
103
113
  setVariable('app_domain', appDomain);
104
114
  let targetFolder = path.join(basePath, hyphenName);
115
+ if (template === 'react-native') {
116
+ // Called through reactNativeInternals so tests can stub the scaffold step.
117
+ yield reactNativeInternals.scaffoldReactNativeApp(targetFolder, { appName, appPrefix, appDomain, hyphenName }, { skipInstall: options.skipInstall });
118
+ return;
119
+ }
105
120
  if (!fs.existsSync(targetFolder)) {
106
121
  fs.mkdirSync(targetFolder);
107
122
  }
@@ -194,6 +209,155 @@ export const createApp = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...ar
194
209
  const startCommand = pm === 'yarn' ? 'yarn start' : 'npm start';
195
210
  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
211
  });
212
+ /**
213
+ * iOS bundle identifier from the app domain and prefix:
214
+ * ('formaestudios.com', 'formae') -> 'com.formaestudios.formae'.
215
+ */
216
+ export function reactNativeBundleId(appDomain, appPrefix) {
217
+ const host = appDomain
218
+ .trim()
219
+ .toLowerCase()
220
+ .replace(/^[a-z][a-z0-9+.-]*:\/\//, '')
221
+ .split(/[/?#]/)[0]
222
+ .replace(/:\d*$/, '');
223
+ const hostLabels = host.split('.').filter(Boolean);
224
+ // A bare host such as `localhost` gets a `com` top level: com.localhost.<prefix>.
225
+ if (hostLabels.length === 1)
226
+ hostLabels.push('com');
227
+ return [...hostLabels.reverse(), appPrefix.toLowerCase()]
228
+ .map((label) => label.replace(/_/g, '-').replace(/[^a-z0-9-]/g, ''))
229
+ .filter(Boolean)
230
+ .join('.');
231
+ }
232
+ /**
233
+ * The prefix becomes a directory, an npm package name, a bundle-ID label and a
234
+ * literal inside a Jest regex; this form is valid in all four unescaped.
235
+ */
236
+ export const RN_PREFIX_PATTERN = /^[a-z][a-z0-9-]*$/;
237
+ /** Throws when the prefix is invalid or the target folder is non-empty. */
238
+ function assertReactNativeScaffoldable(targetFolder, appPrefix) {
239
+ if (typeof appPrefix !== 'string' || !RN_PREFIX_PATTERN.test(appPrefix)) {
240
+ throw new Error(`Invalid app prefix "${appPrefix !== null && appPrefix !== void 0 ? appPrefix : ''}": it must match ${RN_PREFIX_PATTERN.source} ` +
241
+ '(start with a lowercase letter; only lowercase letters, digits and hyphens).');
242
+ }
243
+ if (fs.existsSync(targetFolder) && fs.readdirSync(targetFolder).length > 0) {
244
+ throw new Error(`Target folder ${targetFolder} already exists and is not empty. ` +
245
+ 'Choose another name or remove the folder.');
246
+ }
247
+ }
248
+ /** Placeholder name of the shapes package in defaults/app-react-native. */
249
+ export const RN_SHAPES_TOKEN = 'app-shapes';
250
+ /**
251
+ * Replace every occurrence of `token` in the text files under `folder`
252
+ * (node_modules and files containing a NUL byte are skipped). A literal token
253
+ * replacement, not the ${...} variable substitution.
254
+ */
255
+ function replaceTokenInTextFiles(folder, token, value) {
256
+ if (token === value)
257
+ return;
258
+ for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
259
+ const full = path.join(folder, entry.name);
260
+ if (entry.isDirectory()) {
261
+ if (entry.name !== 'node_modules' && entry.name !== '.git') {
262
+ replaceTokenInTextFiles(full, token, value);
263
+ }
264
+ }
265
+ else if (entry.isFile()) {
266
+ const buffer = fs.readFileSync(full);
267
+ if (buffer.includes(0))
268
+ continue;
269
+ const text = buffer.toString('utf8');
270
+ if (text.includes(token)) {
271
+ fs.writeFileSync(full, text.split(token).join(value));
272
+ }
273
+ }
274
+ }
275
+ }
276
+ const readJSON = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
277
+ const writeJSON = (file, data) => fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n');
278
+ /**
279
+ * Scaffold the React Native monorepo from defaults/app-react-native.
280
+ * Identity is stamped into named fields only; the global ${...} substitution
281
+ * (replaceVariablesInFolder) is never used, so template literals in TypeScript
282
+ * sources stay intact.
283
+ */
284
+ export function scaffoldReactNativeApp(targetFolder_1, id_1) {
285
+ return __awaiter(this, arguments, void 0, function* (targetFolder, id, options = {}) {
286
+ const { appName, appPrefix, appDomain, hyphenName } = id;
287
+ // Validate before writing anything.
288
+ assertReactNativeScaffoldable(targetFolder, appPrefix);
289
+ // lib/esm/ (built) is two levels below the package root; src/ (Jest) is one.
290
+ const templateDir = options.templateDir ||
291
+ [
292
+ path.join(getScriptDir(), '..', '..', 'defaults', 'app-react-native'),
293
+ path.join(getScriptDir(), '..', 'defaults', 'app-react-native'),
294
+ ].find((dir) => fs.existsSync(dir));
295
+ if (!templateDir) {
296
+ throw new Error('React Native template not found (defaults/app-react-native)');
297
+ }
298
+ const shapesName = `${appPrefix}-shapes`;
299
+ log("Creating new Linked React Native app '" + appName + "'");
300
+ // 1. Copy the template.
301
+ fs.copySync(templateDir, targetFolder);
302
+ // 2. Restore dotfiles npm would strip from the CLI tarball.
303
+ renameShippedDotfiles(targetFolder);
304
+ // 3. Rename the shapes package directory.
305
+ const oldShapesDir = path.join(targetFolder, 'packages', RN_SHAPES_TOKEN);
306
+ const shapesDir = path.join(targetFolder, 'packages', shapesName);
307
+ if (fs.existsSync(oldShapesDir) && oldShapesDir !== shapesDir) {
308
+ fs.renameSync(oldShapesDir, shapesDir);
309
+ }
310
+ // 4a. Rename the shapes package token everywhere: workspaces, .gitignore
311
+ // 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);
314
+ // 4b. Stamp the identity fields that are not the shapes token.
315
+ const rootPkgPath = path.join(targetFolder, 'package.json');
316
+ if (fs.existsSync(rootPkgPath)) {
317
+ const rootPkg = readJSON(rootPkgPath);
318
+ rootPkg.name = `${hyphenName}-monorepo`;
319
+ writeJSON(rootPkgPath, rootPkg);
320
+ }
321
+ const mobileDir = path.join(targetFolder, 'apps', 'mobile');
322
+ const appJsonPath = path.join(mobileDir, 'app.json');
323
+ if (fs.existsSync(appJsonPath)) {
324
+ const appJson = readJSON(appJsonPath);
325
+ appJson.expo = appJson.expo || {};
326
+ appJson.expo.name = appName;
327
+ appJson.expo.slug = hyphenName;
328
+ appJson.expo.ios = appJson.expo.ios || {};
329
+ appJson.expo.ios.bundleIdentifier = reactNativeBundleId(appDomain, appPrefix);
330
+ writeJSON(appJsonPath, appJson);
331
+ }
332
+ // 5. Install with npm: the template is an npm workspaces monorepo.
333
+ if (!options.skipInstall) {
334
+ const spinner = ora({
335
+ text: 'Installing dependencies (npm)...',
336
+ spinner: 'dots',
337
+ }).start();
338
+ try {
339
+ yield execPromise('npm install', false, false, {
340
+ cwd: targetFolder,
341
+ maxBuffer: 50 * 1024 * 1024,
342
+ });
343
+ spinner.succeed('Dependencies installed (npm)');
344
+ }
345
+ catch (err) {
346
+ spinner.fail('Could not install dependencies');
347
+ if (err === null || err === void 0 ? void 0 : err.stdout)
348
+ process.stdout.write(err.stdout);
349
+ if (err === null || err === void 0 ? void 0 : err.stderr)
350
+ process.stderr.write(err.stderr);
351
+ throw new Error(`npm install failed. The files are in ${targetFolder}; ` +
352
+ `run \`npm install\` there to retry.`);
353
+ }
354
+ }
355
+ // 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')}`);
357
+ });
358
+ }
359
+ // Indirection so tests can replace the scaffold step when exercising createApp.
360
+ export const reactNativeInternals = { scaffoldReactNativeApp };
197
361
  /** Set or append a `KEY=value` line in a `.env`-style text blob. */
198
362
  function setEnvVar(envText, key, value) {
199
363
  const line = `${key}=${value}`;
@@ -961,6 +1125,42 @@ export const createOntology = (prefix_1, uriBase_1, ...args_1) => __awaiter(void
961
1125
  log(`Added an import of this file from ${chalk.magenta(indexPath)}`);
962
1126
  }
963
1127
  });
1128
+ // The shapes barrel (src/shapes/index.ts) is imported by both the frontend index and
1129
+ // the backend entry, so every shape registers on both boot paths (and materializes into
1130
+ // app-data on boot). `create-shape` adds each new shape here — NOT to the main index.
1131
+ const SHAPES_BARREL_HEADER = `// Shape registry — every shape is imported here so its @linkedShape decorator runs and\n` +
1132
+ `// the shape registers on both boot paths (backend materialization + frontend). \`linked\n` +
1133
+ `// create-shape\` maintains this list.\n//SHAPES\n`;
1134
+ const ensureBackendImportsShapes = function (root = process.cwd()) {
1135
+ const backendPath = path.join(root, 'src', 'backend.ts');
1136
+ if (!fs.existsSync(backendPath))
1137
+ return;
1138
+ let contents = fs.readFileSync(backendPath, 'utf-8');
1139
+ if (contents.includes('./shapes/index'))
1140
+ return;
1141
+ fs.writeFileSync(backendPath, `import './shapes/index.js';\n` + contents);
1142
+ };
1143
+ // Register a shape in the shapes barrel (src/shapes/index.ts), creating it if needed, and
1144
+ // make sure both the main index and backend.ts import the barrel. Idempotent.
1145
+ export const addShapeToBarrel = function (shapeHyphenName, root = process.cwd()) {
1146
+ const shapesDir = path.join(root, 'src', 'shapes');
1147
+ fs.ensureDirSync(shapesDir);
1148
+ const barrelPath = path.join(shapesDir, 'index.ts');
1149
+ let contents = fs.existsSync(barrelPath)
1150
+ ? fs.readFileSync(barrelPath, 'utf-8')
1151
+ : SHAPES_BARREL_HEADER;
1152
+ const line = `import './${shapeHyphenName}.js';`;
1153
+ if (!contents.split(/\n/g).some((l) => l.trim() === line.trim())) {
1154
+ contents = contents.includes('//SHAPES')
1155
+ ? contents.replace('//SHAPES', `//SHAPES\n${line}`)
1156
+ : contents.replace(/\n*$/, '') + `\n${line}\n`;
1157
+ fs.writeFileSync(barrelPath, contents);
1158
+ }
1159
+ // Both boot paths must load the barrel (idempotent).
1160
+ addLineToIndex(`import './shapes/index.js';`, 'shapes', root);
1161
+ ensureBackendImportsShapes(root);
1162
+ return barrelPath;
1163
+ };
964
1164
  // Exported for Shape-Builder reuse (plan-010 T1d.4): CodeShapeSyncService adds
965
1165
  // the `import './shapes/<Shape>.js';` line to a generated app package's index.
966
1166
  export const addLineToIndex = function (line, insertMatchString, root = process.cwd(), insertAtStart = false) {
@@ -1145,12 +1345,11 @@ export const createShape = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...
1145
1345
  //replace variables in some of the copied files
1146
1346
  yield replaceVariablesInFiles(targetFile);
1147
1347
  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
- }
1348
+ // Register the shape in the shapes barrel (src/shapes/index.ts) NOT the main index —
1349
+ // 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).
1351
+ const barrelPath = addShapeToBarrel(hyphenName);
1352
+ log(`Registered the shape in ${chalk.magenta(barrelPath.replace(basePath, ''))}`);
1154
1353
  });
1155
1354
  export const createSetComponent = (name_1, ...args_1) => __awaiter(void 0, [name_1, ...args_1], void 0, function* (name, basePath = process.cwd()) {
1156
1355
  let targetFolder = ensureFolderExists(basePath, 'src', 'components');
@@ -1866,6 +2065,7 @@ export const createPackage = (name_1, uriBase_1, ...args_1) => __awaiter(void 0,
1866
2065
  let { hyphenName, camelCaseName, underscoreName } = setNameVariables(cleanPackageName);
1867
2066
  log("Creating new LINCD package '" + name + "'");
1868
2067
  fs.copySync(path.join(getScriptDir(), '..', '..', 'defaults', 'package'), targetFolder);
2068
+ renameShippedDotfiles(targetFolder);
1869
2069
  //replace variables in some of the copied files
1870
2070
  yield Promise.all([
1871
2071
  'src/index.ts',
@@ -1896,12 +2096,25 @@ export const createPackage = (name_1, uriBase_1, ...args_1) => __awaiter(void 0,
1896
2096
  console.log('yarn probably not working');
1897
2097
  return '';
1898
2098
  }));
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');
2099
+ const setup = planPackageSetup(version.toString(), path.join(getScriptDir(), 'launch.js'));
2100
+ if (setup.yarnrc) {
2101
+ fs.writeFileSync(path.join(targetFolder, '.yarnrc.yml'), setup.yarnrc);
2102
+ }
2103
+ const installed = yield execp(setup.installCommand, true, false, {
2104
+ cwd: targetFolder,
2105
+ })
2106
+ .then(() => true)
2107
+ .catch(() => {
2108
+ console.warn(`Could not install dependencies (${setup.installCommand})`);
2109
+ process.exitCode = 1;
2110
+ return false;
1904
2111
  });
2112
+ if (installed) {
2113
+ yield execp(setup.buildCommand, true, false, { cwd: targetFolder }).catch(() => {
2114
+ console.warn('Dependencies installed, but the initial build failed');
2115
+ process.exitCode = 1;
2116
+ });
2117
+ }
1905
2118
  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
2119
  });
1907
2120
  var getNextVersion = function (version) {