pinstripe 0.29.0 → 0.31.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/README.md +1 -3
- package/lib/apps/docs.js +6 -0
- package/lib/commands/list_apps.js +1 -1
- package/lib/commands/list_views.js +3 -21
- package/lib/component.js +6 -36
- package/lib/database/migrator.js +5 -5
- package/lib/extensions/_file_importer.js +1 -0
- package/lib/extensions/multi-tenant/database/row.js +26 -0
- package/lib/extensions/multi-tenant/database/table.js +30 -0
- package/lib/extensions/multi-tenant/database.js +18 -0
- package/lib/extensions/multi-tenant/index.js +4 -0
- package/lib/extensions/multi-tenant/migrations/1627976174_create_tenant_table_and_add_tenant_id_to_existing_tables.js +20 -0
- package/lib/extensions/multi-tenant/migrations/_file_importer.js +2 -0
- package/lib/extensions/multi-tenant/services/_file_importer.js +2 -0
- package/lib/extensions/multi-tenant/services/database.js +32 -0
- package/lib/markdown.js +1 -1
- package/lib/proof_of_work.js +93 -0
- package/lib/services/cookies.js +1 -1
- package/lib/services/docs.js +93 -0
- package/lib/services/fetch.js +18 -47
- package/lib/services/render_form.js +2 -7
- package/lib/services/render_view.js +1 -1
- package/lib/view.js +6 -1
- package/lib/views/docs/_404.js +16 -0
- package/lib/views/docs/_footer.js +67 -0
- package/lib/views/docs/_header.js +17 -0
- package/lib/views/docs/_hero.js +59 -0
- package/lib/views/docs/_layout.js +118 -0
- package/lib/views/docs/_navbar.js +99 -0
- package/lib/views/docs/_sidebar.js +31 -0
- package/lib/views/docs/default.js +6 -0
- package/lib/views/docs/docs/default.js +24 -0
- package/lib/views/docs/docs/guides/introduction.md +18 -0
- package/lib/views/docs/images/logo.svg +7 -0
- package/lib/views/docs/index.js +16 -0
- package/lib/views/shared/_content.js +1 -5
- package/lib/views/shared/_form.js +17 -28
- package/lib/views/shared/_navbar.js +0 -28
- package/lib/views/shared/_shell/index.js +25 -0
- package/lib/views/shared/_shell/stylesheets/all.css +4 -0
- package/package.json +4 -4
- package/lib/services/session.js +0 -19
- package/lib/views/shared/assets/stylesheets/all.css +0 -4
- /package/lib/views/shared/{assets → _shell}/javascripts/all.js.js +0 -0
- /package/lib/views/shared/{assets → _shell}/javascripts/all.js.map.js +0 -0
- /package/lib/views/shared/{assets → _shell}/stylesheets/global.css +0 -0
- /package/lib/views/shared/{assets → _shell}/stylesheets/reset.css +0 -0
- /package/lib/views/shared/{assets → _shell}/stylesheets/vars.css +0 -0
- /package/lib/views/shared/{assets → _shell}/stylesheets/view.css.js +0 -0
package/README.md
CHANGED
package/lib/apps/docs.js
ADDED
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
console.log('The following apps are available:');
|
|
9
9
|
console.log('');
|
|
10
10
|
App.names.forEach(appName => {
|
|
11
|
-
console.log(` * ${chalk.green(appName)}
|
|
11
|
+
console.log(` * ${chalk.green(appName)}`);
|
|
12
12
|
});
|
|
13
13
|
console.log('');
|
|
14
14
|
}
|
|
@@ -6,31 +6,13 @@ export default {
|
|
|
6
6
|
run(){
|
|
7
7
|
const { extractOptions } = this.cliUtils;
|
|
8
8
|
|
|
9
|
-
const { app } = extractOptions();
|
|
9
|
+
const { app = 'main' } = extractOptions();
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
this.listComposedViews(typeof app == 'string' ? app : 'main');
|
|
13
|
-
} else {
|
|
14
|
-
this.listAllViews();
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
listComposedViews(appName){
|
|
19
|
-
const { viewNames, resolveView } = View.mapperFor(App.create(appName, this.context).compose());
|
|
20
|
-
console.log('');
|
|
21
|
-
console.log(`The following views have been composed for app "${appName}":`);
|
|
22
|
-
console.log('');
|
|
23
|
-
viewNames.forEach(viewName => {
|
|
24
|
-
console.log(` * ${chalk.green(viewName)} -> ${chalk.green(resolveView(viewName))}`);
|
|
25
|
-
});
|
|
26
|
-
console.log('');
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
listAllViews(){
|
|
11
|
+
const { viewNames } = View.mapperFor(App.create(app, this.context).compose());
|
|
30
12
|
console.log('');
|
|
31
13
|
console.log(`The following views are available:`);
|
|
32
14
|
console.log('');
|
|
33
|
-
|
|
15
|
+
viewNames.forEach(viewName => {
|
|
34
16
|
console.log(` * ${chalk.green(viewName)}`);
|
|
35
17
|
});
|
|
36
18
|
console.log('');
|
package/lib/component.js
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { fileURLToPath } from 'url'; // pinstripe-if-client: const fileURLToPath = undefined;
|
|
3
3
|
|
|
4
4
|
import { Class } from './class.js';
|
|
5
|
-
import { TEXT_ONLY_TAGS
|
|
5
|
+
import { TEXT_ONLY_TAGS } from './constants.js';
|
|
6
6
|
import { Inflector } from './inflector.js';
|
|
7
7
|
import { VirtualNode } from './virtual_node.js';
|
|
8
8
|
import { Registry } from './registry.js';
|
|
9
9
|
import { ComponentEvent } from './component_event.js';
|
|
10
10
|
import { Client } from './client.js'; // pinstripe-if-client: const Client = undefined;
|
|
11
|
+
import { generateProofOfWork } from './proof_of_work.js';
|
|
11
12
|
|
|
12
13
|
export const Component = Class.extend().include({
|
|
13
14
|
meta(){
|
|
@@ -462,7 +463,10 @@ export const Component = Class.extend().include({
|
|
|
462
463
|
if(!(otherOptions.body instanceof FormData)) throw new Error(`Proof of work requires form data to be present`);
|
|
463
464
|
const values = {};
|
|
464
465
|
otherOptions.body.forEach((value, key) => values[key] = value);
|
|
465
|
-
otherOptions.body.append('_proofOfWork', await generateProofOfWork(values,
|
|
466
|
+
otherOptions.body.append('_proofOfWork', await generateProofOfWork(values, {
|
|
467
|
+
abortSignal: abortController.signal,
|
|
468
|
+
onProgress: progress => this.trigger('proofOfWorkProgress', { data: progress, bubbles: false })
|
|
469
|
+
}))
|
|
466
470
|
}
|
|
467
471
|
const promises = [
|
|
468
472
|
fetch(normalizedUrl, { signal: abortController.signal, ...otherOptions }),
|
|
@@ -712,38 +716,4 @@ function normalizeVirtualNode(){
|
|
|
712
716
|
}
|
|
713
717
|
}
|
|
714
718
|
|
|
715
|
-
async function generateProofOfWork(values, abortSignal){
|
|
716
|
-
const stringifiedValues = JSON.stringify(values);
|
|
717
|
-
const timestamp = getUTCTimestamp();
|
|
718
|
-
const random = btoa(`${Math.random()}`);
|
|
719
|
-
let counter = 0;
|
|
720
|
-
while(!abortSignal.aborted){
|
|
721
|
-
const candidateSolution = `1:20:${timestamp}:?::${random}:${btoa(`${counter}`)}`;
|
|
722
|
-
const hash = await createSha1Hash(candidateSolution.replace(/\?/, stringifiedValues));
|
|
723
|
-
if(hash.match(/^00000/)) return candidateSolution;
|
|
724
|
-
counter++;
|
|
725
|
-
}
|
|
726
|
-
return '';
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function getUTCTimestamp() {
|
|
730
|
-
const now = new Date();
|
|
731
|
-
|
|
732
|
-
const year = now.getUTCFullYear() % 100;
|
|
733
|
-
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
|
734
|
-
const day = String(now.getUTCDate()).padStart(2, '0');
|
|
735
|
-
const hours = String(now.getUTCHours()).padStart(2, '0');
|
|
736
|
-
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
|
737
|
-
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
|
738
|
-
|
|
739
|
-
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
async function createSha1Hash(input) {
|
|
744
|
-
const buffer = new TextEncoder().encode(input);
|
|
745
|
-
const hashArray = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-1', buffer)));
|
|
746
|
-
return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
747
|
-
}
|
|
748
|
-
|
|
749
719
|
ComponentEvent.Component = Component;
|
package/lib/database/migrator.js
CHANGED
|
@@ -8,20 +8,20 @@ export const Migrator = Class.extend().include({
|
|
|
8
8
|
},
|
|
9
9
|
|
|
10
10
|
async migrate(){
|
|
11
|
-
if(!await this.database.table('
|
|
12
|
-
await this.database.table('
|
|
13
|
-
await
|
|
11
|
+
if(!await this.database.table('appliedMigrations').exists){
|
|
12
|
+
await this.database.table('appliedMigrations', async appliedMigrations => {
|
|
13
|
+
await appliedMigrations.addColumn('schemaVersion', 'integer');
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
const isDevelopmentEnvironment = (process.env.NODE_ENV || 'development') == 'development';
|
|
17
17
|
const migrations = Migration.names.map(name => Migration.for(name)).sort((a, b) => a.schemaVersion - b.schemaVersion);
|
|
18
18
|
for(let i in migrations){
|
|
19
19
|
const migration = migrations[i];
|
|
20
|
-
const isMigrationApplied = await this.database.table('
|
|
20
|
+
const isMigrationApplied = await this.database.table('appliedMigrations').where({ schemaVersion: migration.schemaVersion }).count() > 0;
|
|
21
21
|
if(!isMigrationApplied){
|
|
22
22
|
if(isDevelopmentEnvironment) console.log(`Applying migration: ${migration.name}`);
|
|
23
23
|
await migration.new(this.database).migrate();
|
|
24
|
-
await this.database.table('
|
|
24
|
+
await this.database.table('appliedMigrations').insert({ schemaVersion: migration.schemaVersion });
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
import { Table, Row } from 'pinstripe/database';
|
|
3
|
+
|
|
4
|
+
Row.include({
|
|
5
|
+
meta(){
|
|
6
|
+
const { initialize, update } = this.prototype;
|
|
7
|
+
|
|
8
|
+
this.include({
|
|
9
|
+
initialize(database, fields, exists){
|
|
10
|
+
const columns = Table.for(this.constructor.collectionName).columns;
|
|
11
|
+
if(columns.tenantId && database.tenant){
|
|
12
|
+
return initialize.call(this, database, { ...fields, tenantId: database.tenant.id }, exists);
|
|
13
|
+
}
|
|
14
|
+
return initialize.call(this, database, fields, exists);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
update(fields){
|
|
18
|
+
const columns = Table.for(this.constructor.collectionName).columns;
|
|
19
|
+
if(columns.tenantId && this.database.tenant){
|
|
20
|
+
return update.call(this, { ...fields, tenantId: this.database.tenant.id });
|
|
21
|
+
}
|
|
22
|
+
return update.call(this, fields);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import { Table } from 'pinstripe/database';
|
|
3
|
+
|
|
4
|
+
Table.include({
|
|
5
|
+
meta(){
|
|
6
|
+
const { create, initialize } = this.prototype;
|
|
7
|
+
|
|
8
|
+
this.include({
|
|
9
|
+
async create(...args){
|
|
10
|
+
if(this.exists) return;
|
|
11
|
+
await create.call(this, ...args);
|
|
12
|
+
if(this.constructor.name == 'appliedMigrations') return;
|
|
13
|
+
if(this.constructor.name == 'tenants') return;
|
|
14
|
+
await this.addColumn('tenantId', 'foreign_key');
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
initialize(...args){
|
|
18
|
+
const out = initialize.call(this, ...args);
|
|
19
|
+
if(this.constructor.columns.tenantId && this.database.scopedByTenant){
|
|
20
|
+
if(this.database.tenant){
|
|
21
|
+
this.where({ tenantId: this.database.tenant.id });
|
|
22
|
+
} else {
|
|
23
|
+
this.where('1 = 2')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import { Database } from 'pinstripe';
|
|
3
|
+
|
|
4
|
+
Database.include({
|
|
5
|
+
meta(){
|
|
6
|
+
const { singleton } = this.prototype;
|
|
7
|
+
|
|
8
|
+
this.include({
|
|
9
|
+
singleton(...args){
|
|
10
|
+
if(this.tenant) return singleton.call(this, ...args);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
get scopedByTenant(){
|
|
16
|
+
return this.hasOwnProperty('tenant');
|
|
17
|
+
}
|
|
18
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
export default {
|
|
3
|
+
async migrate(){
|
|
4
|
+
await this.database.table('tenants', async tenants => {
|
|
5
|
+
await tenants.addColumn('name', 'string', { index: true });
|
|
6
|
+
await tenants.addColumn('host', 'string', { index: true });
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const tableNames = Object.keys(this.database.info).filter(name => this.database.info[name] == 'table');
|
|
10
|
+
|
|
11
|
+
while(tableNames.length){
|
|
12
|
+
const tableName = tableNames.shift();
|
|
13
|
+
if(tableName == 'appliedMigrations') continue;
|
|
14
|
+
if(tableName == 'tenants') continue;
|
|
15
|
+
|
|
16
|
+
if(this.database[tableName].constructor.columns.tenantId) continue;
|
|
17
|
+
await this.database[tableName].addColumn('tenantId', 'foreign_key');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
import { Database } from 'pinstripe';
|
|
3
|
+
import { Client } from "pinstripe/database";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
create(){
|
|
7
|
+
return this.defer(async () => {
|
|
8
|
+
if(!this.context.root.databaseClient){
|
|
9
|
+
this.context.root.databaseClient = Client.new(await this.config.database);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const out = await Database.new(this.context.root.databaseClient);
|
|
13
|
+
|
|
14
|
+
if(out.info.tenants){
|
|
15
|
+
const headers = this.initialParams._headers;
|
|
16
|
+
const hostname = this.initialParams._url.hostname;
|
|
17
|
+
const host = (headers['host'] || hostname).replace(/\:\d+$/, '').toLowerCase();
|
|
18
|
+
const { primaryDomain = '' } = await this.config;
|
|
19
|
+
const domainSuffix = `\.${primaryDomain}`.toLowerCase();
|
|
20
|
+
|
|
21
|
+
if(host.endsWith(domainSuffix)){
|
|
22
|
+
const name = host.substr(0, host.length - domainSuffix.length);
|
|
23
|
+
out.tenant = await out.tenants.where({ name }).first();
|
|
24
|
+
} else {
|
|
25
|
+
out.tenant = await out.tenants.where({ host }).first();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return out;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
package/lib/markdown.js
CHANGED
|
@@ -39,7 +39,7 @@ export const Markdown = Class.extend().include({
|
|
|
39
39
|
paragraph.attributes = {
|
|
40
40
|
...paragraph.attributes,
|
|
41
41
|
'data-component': 'pinstripe-frame',
|
|
42
|
-
'data-url': `/
|
|
42
|
+
'data-url': `/_markdown_slash_blocks/${name}?args=${encodeURIComponent(args)}`,
|
|
43
43
|
};
|
|
44
44
|
paragraph.children = [];
|
|
45
45
|
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
|
|
2
|
+
import crypto from 'crypto' // pinstripe-if-client: const crypto = window.crypto;
|
|
3
|
+
|
|
4
|
+
import { Class } from './class.js';
|
|
5
|
+
import { Singleton } from './singleton.js';
|
|
6
|
+
|
|
7
|
+
const HASH_CASH_TARGET = Math.pow(2, 32 - 20);
|
|
8
|
+
|
|
9
|
+
export const ProofOfWork = Class.extend().include({
|
|
10
|
+
meta(){
|
|
11
|
+
this.include(Singleton)
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
async generateProofOfWork(input, options = {}){
|
|
15
|
+
const { difficulty = 1, steps = 1000, onProgress = () => {}, abortSignal = { aborted: false } } = options;
|
|
16
|
+
const inputHash = await this.createSha1Hash(JSON.stringify(input));
|
|
17
|
+
const salt = await this.createSha1Hash(Math.random());
|
|
18
|
+
const timestamp = this.getUTCTimestamp();
|
|
19
|
+
const target = this.calculateTarget(difficulty, steps);
|
|
20
|
+
const solution = [];
|
|
21
|
+
|
|
22
|
+
let counter = -1;
|
|
23
|
+
for(let i = 1; i <= steps && !abortSignal.aborted; i++){
|
|
24
|
+
while(!abortSignal.aborted){
|
|
25
|
+
counter++;
|
|
26
|
+
const hash = await this.createSha1Hash(`${difficulty}:${steps}:${inputHash}:${salt}:${timestamp}:${counter}`);
|
|
27
|
+
const integers = this.hexToIntegers(hash);
|
|
28
|
+
if(integers[0] <= target) break;
|
|
29
|
+
}
|
|
30
|
+
solution.push(counter);
|
|
31
|
+
const progressPercentage = Math.floor(i * (100 / steps) * 100) / 100;
|
|
32
|
+
onProgress(progressPercentage);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if(abortSignal.aborted) return;
|
|
36
|
+
|
|
37
|
+
return JSON.stringify({
|
|
38
|
+
salt,
|
|
39
|
+
timestamp,
|
|
40
|
+
solution,
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async verifyProofOfWork(input, proofOfWork, options = {}){
|
|
45
|
+
const { difficulty = 1 } = options;
|
|
46
|
+
const { salt, timestamp, solution } = JSON.parse(proofOfWork);
|
|
47
|
+
const steps = solution.length;
|
|
48
|
+
const inputHash = await this.createSha1Hash(JSON.stringify(input));
|
|
49
|
+
const target = this.calculateTarget(difficulty, steps);
|
|
50
|
+
for(let i = 0; i < steps; i++){
|
|
51
|
+
const counter = solution[i];
|
|
52
|
+
const hash = await this.createSha1Hash(`${difficulty}:${steps}:${inputHash}:${salt}:${timestamp}:${counter}`.toString(), true);
|
|
53
|
+
const integers = this.hexToIntegers(hash);
|
|
54
|
+
if(integers[0] > target) return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async createSha1Hash(input) {
|
|
60
|
+
const buffer = new TextEncoder().encode(input);
|
|
61
|
+
const hashArray = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-1', buffer)));
|
|
62
|
+
return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
hexToIntegers(hex){
|
|
66
|
+
const out = [];
|
|
67
|
+
for(let i = 0; i < hex.length; i += 8){
|
|
68
|
+
out.push(parseInt(hex.slice(i, i + 8), 16));
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
calculateTarget(difficulty, steps){
|
|
74
|
+
return (HASH_CASH_TARGET / difficulty) * steps;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
getUTCTimestamp() {
|
|
78
|
+
const now = new Date();
|
|
79
|
+
|
|
80
|
+
const year = now.getUTCFullYear() % 100;
|
|
81
|
+
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
|
82
|
+
const day = String(now.getUTCDate()).padStart(2, '0');
|
|
83
|
+
const hours = String(now.getUTCHours()).padStart(2, '0');
|
|
84
|
+
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
|
85
|
+
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
|
86
|
+
|
|
87
|
+
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const generateProofOfWork = (...args) => ProofOfWork.instance.generateProofOfWork(...args);
|
|
92
|
+
|
|
93
|
+
export const verifyProofOfWork = (...args) => ProofOfWork.instance.verifyProofOfWork(...args);
|
package/lib/services/cookies.js
CHANGED
|
@@ -3,7 +3,7 @@ export default {
|
|
|
3
3
|
create(){
|
|
4
4
|
if(!this.context.root.hasOwnProperty('cookies')){
|
|
5
5
|
const cookies = {};
|
|
6
|
-
const cookieHeader = this.
|
|
6
|
+
const cookieHeader = this.initialParams._headers?.cookie;
|
|
7
7
|
if(cookieHeader){
|
|
8
8
|
cookieHeader.split(/;/).forEach(cookie => {
|
|
9
9
|
const matches = cookie.trim().match(/^([^=]+)=(.*)$/);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { realpath, readFile } from 'fs/promises';
|
|
4
|
+
import { registries } from 'pinstripe/util';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
async create(){
|
|
8
|
+
if(!this.context.root.docs){
|
|
9
|
+
this.context.root.docs = {
|
|
10
|
+
apps: await this.extractDocs('App', 'apps'),
|
|
11
|
+
commands: await this.extractDocs('Command', 'commands'),
|
|
12
|
+
components: await this.extractDocs('Component', 'components'),
|
|
13
|
+
migrations: await this.extractDocs('Migration', 'migrations'),
|
|
14
|
+
models: await this.extractDocs('Row', 'models'),
|
|
15
|
+
services: await this.extractDocs('ServiceFactory', 'services'),
|
|
16
|
+
views: await this.extractDocs('View', 'views'),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return this.context.root.docs;
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async extractDocs(name, namespace){
|
|
25
|
+
const out = {};
|
|
26
|
+
const registry = registries[name];
|
|
27
|
+
const { names } = registry;
|
|
28
|
+
for(let name of names){
|
|
29
|
+
const slug = `${namespace}/${name}`;
|
|
30
|
+
|
|
31
|
+
const Class = registry.for(name);
|
|
32
|
+
|
|
33
|
+
const filePath = Class.filePaths[Class.filePaths.length - 1];
|
|
34
|
+
|
|
35
|
+
if(!filePath || !filePath.match(/\.(js|css)/)) continue;
|
|
36
|
+
|
|
37
|
+
const packageRootPath = await this.getPackageRootPath(filePath);
|
|
38
|
+
|
|
39
|
+
if(packageRootPath == undefined) continue;
|
|
40
|
+
|
|
41
|
+
const packageJson = await this.getPackageJson(packageRootPath);
|
|
42
|
+
|
|
43
|
+
const packageName = packageJson.name;
|
|
44
|
+
|
|
45
|
+
const packageRelativePath = filePath.substr(packageRootPath.length).replace(/^\//, '');
|
|
46
|
+
const repositoryRelativePath = `${packageJson.repository?.directory ? `${packageJson.repository.directory}/` : ''}${packageRelativePath}`;
|
|
47
|
+
|
|
48
|
+
const code = (await readFile(filePath)).toString('utf8');
|
|
49
|
+
|
|
50
|
+
const markdown = code.split(/\/\*\//).map((segment, i) => {
|
|
51
|
+
if(i % 2 == 0 && segment.trim().length){
|
|
52
|
+
return `\`\`\`\n${segment}\n\`\`\``;
|
|
53
|
+
}
|
|
54
|
+
return segment;
|
|
55
|
+
}).join('');
|
|
56
|
+
|
|
57
|
+
out[name] = {
|
|
58
|
+
name,
|
|
59
|
+
slug,
|
|
60
|
+
packageName,
|
|
61
|
+
packageRelativePath,
|
|
62
|
+
repositoryRelativePath,
|
|
63
|
+
markdown
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async getPackageRootPath(path){
|
|
70
|
+
const packageJsonPath = (await this.findInPath('package.json', path.replace(/[^/]*$/, ''))).shift();
|
|
71
|
+
if(!packageJsonPath) return;
|
|
72
|
+
return packageJsonPath.replace(/package.json$/, '');
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
async getPackageJson(packageRootPath){
|
|
76
|
+
return JSON.parse((await readFile(`${packageRootPath}package.json`)).toString('utf8'));
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async findInPath(offset, base){
|
|
80
|
+
const out = [];
|
|
81
|
+
while(base) {
|
|
82
|
+
const candidatePath = `${base}/${offset}`;
|
|
83
|
+
if(existsSync(candidatePath)){
|
|
84
|
+
out.push(await realpath(candidatePath));
|
|
85
|
+
}
|
|
86
|
+
if(base == '/'){
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
base = await realpath(`${base}/..`);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
};
|
package/lib/services/fetch.js
CHANGED
|
@@ -15,28 +15,18 @@ export default {
|
|
|
15
15
|
const normalizedParams = this.normalizeParams(params);
|
|
16
16
|
this.context.params = normalizedParams;
|
|
17
17
|
|
|
18
|
-
const viewName = normalizedParams._url.pathname.replace(/^\/|\/$/g, '');
|
|
18
|
+
const viewName = normalizedParams._url.pathname.replace(/^\/|\/$/g, '') || 'index';
|
|
19
19
|
|
|
20
20
|
let out = await this.renderGuardViews(viewName, normalizedParams);
|
|
21
|
-
if(out)
|
|
22
|
-
return out;
|
|
23
|
-
}
|
|
21
|
+
if(out) return out;
|
|
24
22
|
|
|
25
|
-
if(!viewName.match(/(^|\/)_
|
|
26
|
-
out = this.normalizeResponse(await this.renderView(viewName
|
|
27
|
-
if(out)
|
|
28
|
-
return out;
|
|
29
|
-
}
|
|
30
|
-
out = this.normalizeResponse(await this.renderView(viewName, normalizedParams));
|
|
31
|
-
if(out){
|
|
32
|
-
return out;
|
|
33
|
-
}
|
|
23
|
+
if(!viewName.match(/(^|\/)_[^\/]+(|\/index)$/)){
|
|
24
|
+
out = this.normalizeResponse(await this.app.renderView(viewName, normalizedParams));
|
|
25
|
+
if(out) return out;
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
out = await this.renderDefaultViews(viewName, normalizedParams);
|
|
37
|
-
if(out)
|
|
38
|
-
return out;
|
|
39
|
-
}
|
|
29
|
+
if(out) return out;
|
|
40
30
|
|
|
41
31
|
return [404, {'content-type': 'text/plain'}, ['Not found']];
|
|
42
32
|
},
|
|
@@ -44,22 +34,12 @@ export default {
|
|
|
44
34
|
async renderGuardViews(viewName, params){
|
|
45
35
|
const viewNameSegments = viewName != '' ? viewName.split(/\//) : [];
|
|
46
36
|
|
|
47
|
-
const candidateIndexView = [...viewNameSegments, 'index'].join('/');
|
|
48
|
-
const candidateDefaultView = [...viewNameSegments, 'default'].join('/');
|
|
49
|
-
|
|
50
|
-
if(!this.app.isView(candidateIndexView) && !this.app.isView(candidateDefaultView)){
|
|
51
|
-
viewNameSegments.pop();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
37
|
const prefixSegments = [];
|
|
55
38
|
while(true){
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if(viewNameSegments.length == 0){
|
|
61
|
-
break;
|
|
62
|
-
}
|
|
39
|
+
const candidateGuardViewName = prefixSegments.length ? [...prefixSegments, 'guard'].join('/') : 'guard';
|
|
40
|
+
const out = this.normalizeResponse(await this.app.renderView(candidateGuardViewName, params));
|
|
41
|
+
if(out) return out;
|
|
42
|
+
if(viewNameSegments.length == 0) break;
|
|
63
43
|
prefixSegments.push(viewNameSegments.shift());
|
|
64
44
|
}
|
|
65
45
|
},
|
|
@@ -67,28 +47,19 @@ export default {
|
|
|
67
47
|
async renderDefaultViews(viewName, params){
|
|
68
48
|
const prefixSegments = viewName != '' ? viewName.split(/\//) : [];
|
|
69
49
|
while(true){
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if(prefixSegments.length == 0){
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
50
|
+
const candidateDefaultViewName = prefixSegments.length ? [...prefixSegments, 'default'].join('/') : 'default';
|
|
51
|
+
const out = this.normalizeResponse(await this.app.renderView(candidateDefaultViewName, params));
|
|
52
|
+
if(out) return out;
|
|
53
|
+
if(prefixSegments.length == 0) break;
|
|
77
54
|
prefixSegments.pop();
|
|
78
55
|
}
|
|
79
56
|
},
|
|
80
57
|
|
|
81
58
|
normalizeParams(params){
|
|
82
59
|
const out = { ...params }
|
|
83
|
-
if(!out._method)
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
if(!params._url){
|
|
87
|
-
out._url = new URL('http://localhost')
|
|
88
|
-
}
|
|
89
|
-
if(!params._headers){
|
|
90
|
-
out._headers = {};
|
|
91
|
-
}
|
|
60
|
+
if(!out._method) out._method = 'get';
|
|
61
|
+
if(!params._url) out._url = new URL('http://localhost');
|
|
62
|
+
if(!params._headers) out._headers = {};
|
|
92
63
|
return out;
|
|
93
64
|
},
|
|
94
65
|
|
|
@@ -108,7 +79,7 @@ export default {
|
|
|
108
79
|
const normalizedHeaders = {};
|
|
109
80
|
Object.keys(headers).forEach(name => {
|
|
110
81
|
normalizedHeaders[name.toLowerCase()] = headers[name];
|
|
111
|
-
})
|
|
82
|
+
});
|
|
112
83
|
|
|
113
84
|
return [ status, normalizedHeaders, body ];
|
|
114
85
|
}
|
|
@@ -2,6 +2,7 @@ import * as crypto from 'crypto';
|
|
|
2
2
|
|
|
3
3
|
import { ValidationError } from '../validation_error.js';
|
|
4
4
|
import { Inflector } from '../inflector.js';
|
|
5
|
+
import { verifyProofOfWork } from '../proof_of_work.js';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
create(){
|
|
@@ -24,7 +25,7 @@ export default {
|
|
|
24
25
|
try {
|
|
25
26
|
if(requiresProofOfWork){
|
|
26
27
|
if(!this.params._proofOfWork) throw new ValidationError({ _proofOfWork: 'Must not be blank' });
|
|
27
|
-
if(!
|
|
28
|
+
if(!verifyProofOfWork(values, this.params._proofOfWork)) throw new ValidationError({ _proofOfWork: 'Must be a valid stamp' });
|
|
28
29
|
}
|
|
29
30
|
return await formAdapter.submit(values, success) || this.renderHtml`
|
|
30
31
|
<span data-component="pinstripe-anchor" data-target="_parent">
|
|
@@ -74,12 +75,6 @@ export default {
|
|
|
74
75
|
submitTitle,
|
|
75
76
|
cancelTitle
|
|
76
77
|
});
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
verifyProofOfWork(proofOfWork, values){
|
|
80
|
-
const hash = crypto.createHash('sha1').update(proofOfWork.replace(/\?/, JSON.stringify(values)), 'binary').digest('hex');
|
|
81
|
-
if(hash.match(/^00000/)) return true;
|
|
82
|
-
return false;
|
|
83
78
|
}
|
|
84
79
|
}
|
|
85
80
|
|