@_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.
- package/CHANGELOG.md +63 -0
- package/README.md +48 -0
- package/defaults/app-react-native/README.md +56 -0
- package/defaults/app-react-native/apps/mobile/App.tsx +26 -0
- package/defaults/app-react-native/apps/mobile/__tests__/linkedDefaults.test.tsx +65 -0
- package/defaults/app-react-native/apps/mobile/__tests__/shapes.test.ts +27 -0
- package/defaults/app-react-native/apps/mobile/app.json +14 -0
- package/defaults/app-react-native/apps/mobile/gitignore.template +41 -0
- package/defaults/app-react-native/apps/mobile/index.ts +8 -0
- package/defaults/app-react-native/apps/mobile/metro.config.js +17 -0
- package/defaults/app-react-native/apps/mobile/package.json +41 -0
- package/defaults/app-react-native/apps/mobile/src/shell/linkedDefaults.tsx +10 -0
- package/defaults/app-react-native/apps/mobile/tsconfig.json +8 -0
- package/defaults/app-react-native/gitignore.template +6 -0
- package/defaults/app-react-native/package.json +14 -0
- package/defaults/app-react-native/packages/app-shapes/package.json +10 -0
- package/defaults/app-react-native/packages/app-shapes/src/index.ts +2 -0
- package/defaults/app-react-native/packages/app-shapes/src/package.ts +10 -0
- package/defaults/app-react-native/packages/app-shapes/src/shapes/Example.ts +17 -0
- package/defaults/app-react-native/packages/app-shapes/tsconfig.json +12 -0
- package/defaults/app-react-native/services/api/README.md +4 -0
- package/defaults/app-react-native/services/api/package.json +5 -0
- package/defaults/package/npmignore.template +6 -0
- package/defaults/package/src/backend.ts +5 -0
- package/defaults/package/src/data/example-ontology.json +10 -0
- package/defaults/package/src/data/example-ontology.json.d.ts +1 -0
- package/defaults/package/src/index.ts +8 -0
- package/defaults/package/src/ontologies/example-ontology.ts +42 -0
- package/defaults/package/src/package.ts +16 -0
- package/defaults/package/src/shapes/index.ts +5 -0
- package/defaults/package/src/types.ts +4 -0
- package/defaults/package/tsconfig.json +17 -0
- package/defaults/setup-publish/dual-branch/github/workflows/publish.yml +29 -15
- package/lib/esm/cli-methods.d.ts +34 -0
- package/lib/esm/cli-methods.d.ts.map +1 -1
- package/lib/esm/cli-methods.js +224 -11
- package/lib/esm/cli-methods.js.map +1 -1
- package/lib/esm/cli.d.ts.map +1 -1
- package/lib/esm/cli.js +9 -1
- package/lib/esm/cli.js.map +1 -1
- package/lib/esm/commands/setup-publish.js +23 -2
- package/lib/esm/commands/setup-publish.js.map +1 -1
- package/lib/esm/utils/packageSetup.d.ts +19 -0
- package/lib/esm/utils/packageSetup.d.ts.map +1 -0
- package/lib/esm/utils/packageSetup.js +23 -0
- package/lib/esm/utils/packageSetup.js.map +1 -0
- package/lib/esm/utils/shippedDotfiles.d.ts +2 -0
- package/lib/esm/utils/shippedDotfiles.d.ts.map +1 -0
- package/lib/esm/utils/shippedDotfiles.js +21 -0
- package/lib/esm/utils/shippedDotfiles.js.map +1 -0
- package/lib/esm/vite-config.d.ts +14 -0
- package/lib/esm/vite-config.d.ts.map +1 -1
- package/lib/esm/vite-config.js +7 -3
- package/lib/esm/vite-config.js.map +1 -1
- package/package.json +9 -1
- package/.changeset/README.md +0 -20
- package/.changeset/config.json +0 -14
- package/.github/workflows/changeset-check.yml +0 -44
- package/.github/workflows/ci.yml +0 -51
- package/.github/workflows/publish.yml +0 -77
- package/.prettierignore +0 -4
- package/.prettierrc.json +0 -24
- package/babel.config.cjs +0 -11
- package/docs/architecture/vite-runtime-config.md +0 -57
- package/jest.config.cjs +0 -33
- package/lib-template/cjs/package.json +0 -3
- package/lib-template/esm/package.json +0 -3
- package/test-results/.last-run.json +0 -6
- package/test-results/create-app-Person-fixture-add-/342/206/222-edit-/342/206/222-delete-round-trip/error-context.md +0 -16
- package/test-results/create-app-Person-fixture-add-/342/206/222-edit-/342/206/222-delete-round-trip/trace.zip +0 -0
- package/tests/e2e/create-app.spec.ts +0 -157
- package/tests/unit/safeYarn.test.ts +0 -55
- package/tsconfig-cjs.json +0 -10
- 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,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-
|
|
140
|
-
name:
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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}} }}
|
package/lib/esm/cli-methods.d.ts
CHANGED
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"cli-methods.d.ts","sourceRoot":"","sources":["../../src/cli-methods.ts"],"names":[],"mappings":"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"}
|
package/lib/esm/cli-methods.js
CHANGED
|
@@ -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
|
-
//
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
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) {
|